我们想要做一个插件,能够直接在界面上就能显示该页面和该事件的pv和uv(据说淘宝就有这种功能),那么就必须将埋点数据和控件进行一一对应。然而我们是行为埋点,很多东西并不会直接绑定控件,所以如果要做这种效果则必然需要对埋点架构进行重新设计,所以这里采用了一个小技巧,来实现大部分控件与埋点的绑定。
埋点方案
一般来说,数据埋点可以分为两种。自动化埋点和手动埋点,各有各的好处和缺点。
自动化埋点
一般来说,自动化埋点都是依据界面层次结构或者控件标签等来进行区分与标记的。
这种方式的好处是避免了大量人工埋点带来的繁琐和错误,也减少了代码的复杂度,同时又保证了非常详细的用户行为。
但是这种方式的缺点也是比较明显,对于功能不是那么稳定的情况下,埋点标记可能会经常变动,不易于后期的统计。同时这种方式对于埋点数据的整理也比较麻烦,可能就需要借助开篇所说的工具来获取。同时对于一些需要数据参数的场景,可能需要人为或者特殊场景下的处理。这种方式对于一个复杂的交互动作,则不能很好的统计,需要后期根据多个连续的统计点来确定一个动作。
对于这种方案的一个优化建议是,限制其自动采集的范畴,弱化和简化其全局性的采集,将自动化限制在一个特点的范围内或者框架内,这样会减少很多的垃圾数据以及更加精准的采集。这种方式就急需要一种工具来整理和归纳埋点数据了。
手动行为埋点
手动行为埋点不需要任何的标记或者swizzle,是非常简单与灵活的一种,在需要的地方,比如事件触发的地方,加入埋点就可以了。但这也存在非常大的问题。
- 埋点数据的整理,随着时间的推进,数据将是非常庞大与混乱的。
- 代码的侵入性,可能会在代码的很多地方都充满着这种恶性的统计代码,导致难以重用以及难以抽离成组件。
- 维护性低,经历多个版本以及多个人员的迭代,维护会越来越困难,可能会出现埋点数据错误的情况,而且无法对线上的数据进行修复。
插件需求
以上的两种方案各有优劣,自动埋点可以直接将控件和埋点绑定,那么我们就很容易的可以将一个页面上所有的埋点给“扫描出来”,并且标记出各个控件,个人猜想阿里的方案就是这样的。
但是由于我们的项目变动较大,界面也不稳定,所以采用的是手动行为埋点的方案,那么事实上控件与埋点是完全独立的,也就不能“扫描出来”。我们也不可能对目前埋点方案进行如此大规模的重构,那么如何将控件与埋点进行绑定呢?这里用到一个小技巧。
首先我们分析我们的场景:
- 大部分埋点都是在点击后才触发的。
- 点击,和埋点触发是顺序关系,且这中间一般不会有其他的点击操作了,也就是说可以认为是同步关系。
- 这种场景满足了我们90%以上的需求,也就是很少有比这种更加复杂的埋点了。
那么我们可以想到首先记录点击的控件,然后观察埋点数据,如果产生顺序的关系,那么我们就可以认为两者是绑定关系。那么我们就可以通过一次自己的点击来获取该控件的埋点信息了。
思路
- hook UIWindow的
hitTest
方法,将得到的视图记录下来。 - 在埋点方法中增加监听点,等待用户点击触发埋点。
- 将最近的两者绑定起来,可以使用弱引用表来保存,以防内存泄露。
这里需要注意的几个问题是埋点要过滤掉一些比较常见的非点击事件,来避免出现很多的误判。
在UICollectionView
和UITableView
中会比较特殊,为了避免reuse view
给我们的视图绑定带来错误的结果,需要hook - prepareForReuse
,在这里移除绑定关系。
有些点击可能会打开新的页面,那么我们也可以增加一个锁定页面跳转的功能,hook Controller
的push
和present
方法成为空操作,就可以禁用跳转了。重新恢复也很简单,再次swizzle就可以替换回来了。
最后
这种方法简单对目前的代码结构几乎没有影响,虽然有可能出现部分误判的情况,但作为一个debug的测试工具已经足够了。