运营人员在前台设置好的埋点信息传输到客户端后,是通过什么样的方式找到特定的控件并绑定上去的呢?如果该控件在应用的生命周期中发生了一些变化,绑定在其上的触发事件会做什么样的反应呢?
事件绑定
先给出服务端与客户端在事件绑定模拟使用的协议格式文件,我们可以猜想出客户端是通过协议中的path字段来定位事件应该绑定的控件对象。这里给出一个事件格式作参考:
"event_bindings": [
{
"control_event": 64,
"table_delegate": null,
"event_type": "ui_control",
"event_name": "P2BtnA01",
"control_target": null,
"path": "/UINavigationController/SecondViewController/UIView/UIButton[(mp_fingerprintVersion >= 1 AND mp_varE == \"954f8a8a7b085efd097f432e98b16a9d97ea2277\")]"
}
]```
客户端会将该JSON协议解析成MPEventBinding对象数组,将path解析成MPObjectSelector对象。整个事件绑定的大致类图如下:
![事件绑定](http://upload-images.jianshu.io/upload_images/1910166-ef73435e20ea003a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
MPEventBinding类中提供了execute与stop两个子类需要覆写的接口。execute的主要作用是在筛选出的控件上绑定后续要发的事件。而stop的作用正好相反,是从已经绑定了事件的控件上删除绑定事件。不同的控件有着不同的触发方式,这里抽象成了MPUIControlBinding与MPUITableViewBinding。
对于UIControl对象,使用了addTarget:action:forControlEvents:将binding对象及行为与控件绑定在一块。在用户点击按键时,自然触发绑定事件。同时需要覆写UIView的didMoveToWindow和didMoveToSuperview方法,用于在新的UIControl对象创建时判断时否需要绑定现有的binding对象。
对于UITableView对象,直接覆写tableView:didSelectRowAtIndexPath:方法,在用户选择cell时判断是否有绑定事件可以触发并上报。
## 控件筛选算法
在上面的协议格式的path字段中,可以看出控件是通过从根元素UIViewController到自身的路径树来标识的。在MPObjectSelector类中的nextFilter方法中,该字符串会根据‘/’被解析成若干个MPObectFilter对象保存在filters数组中。每一个filter表示树中的一层。匹配方向有自上而下、自下及上两种方式,遍历方法参见MPObjectFilter类中的getChildrenOfObject:ofType:和getParentsOfObject:方法。
匹配枝干层主要依靠对象类名是否与filter对象中name一致。匹配叶层控件是除了类名一致外,还需要控件所需要的特征值与filter的predicate一致,该特征值正是我们在上一篇文章中获取到的控件关键信息。用于匹配的特征值代码位于UIView+MPHelpers.h/m类别中,这里列举部分如下:
-(NSString*)mp_text
{
//控件为UILable时取label的title
//控件为UIButton时,取Normal状态下title
//其它控件尝试获取其title
}
-(NSString)mp_imageFingerprint
{
//控件为UIButton或UITableBarButton时,取控件image的88像素转化成NSData数据,再压缩成base64字符串
}```
MPSwizzler
这个类是事件绑定到控件的核心代码,它主要使用了IOS的runtime机制,在应用原有的逻辑行为执行完毕后又额外的执行了其它新增的行为。其具体原理机制这里就不在作详细介绍,这是给出一个较为详细说明这种机制的链接。