iOS 无侵入埋点的实现

埋点
  • 概念:在iOS开发中,埋点可以解决两大类问题,一是了解用户使用App的行为,二是降低分析线上问题的难度。
  • 常见的埋点方式
    常见的埋点方式主要包括代码埋点、可视化埋点和无埋点三种
  1. 代码埋点主要就是通过手写代码的形式埋点,能很精确的在需要埋点的代码处加上埋点的代码,可以很方便的记录当前环境的变量值,方便调试,并跟踪埋点内容。但存在开发工作量大,并且埋点代码到处都是,后期难以维护等问题。
  2. 可视化埋点,就是将埋点增加和修改的工作可视化了,提升了增加和维护埋点的体验。
  3. 无埋点,并不是不需要埋点,而更确切的说是“全埋点”,而且埋点代码也不会出现在业务代码中,容易管理和维护。它的缺点在于,埋点成本高,后期的解析也比较复杂,再加上view_path的不确定性。所以这种方案不能解决所有的埋点需求,但对于大量通用的埋点需求来说,能够节省大量的开发和维护成本。

其中,可视化埋点和无埋点,都属于无侵入埋点的内容,因为它们都不需要在工程代码中写入埋点代码。所以,采用这样的无侵入埋点方案,既可以做到埋点被统一处理,又可以实现和工程代码的解耦。

运行时方法替换方式进行埋点

iOS开发中,最常见的三种埋点,就是对页面进入次数、页面停留时间、点击事件的埋点。对于这三种常见情况,我们都可以通过运行时方法替换技术来插入埋点的代码,以实现无侵入的埋点方式。

  1. 对于页面曝光埋点,则需要对VC的生命周期方法进行埋点,交换VC的viewWillAppear、viewDidAppear、viewWillDisappear、viewDidDisappear等方法,这样的我们就可以在交互的方法里面加上埋点代码。
    如果区分不同的UIViewController呢?我们可以使用NSStringFromClass([self class])类名字符串来标识不同的UIViewController。

  2. 对于点击事件来说,则需要替换Button的点击事件方法sendAction:to:forEvent,以调用埋点代码。
    和UIViewController生命周期埋点不同的是,UIButton在一个视图类中可能有多个不同的继承类,相同UIButton的子类在不同视图类的埋点也要区分开,这时候,我们可以通过action选择器名NSStringFromSelector(action)+视图类名NSStringFromClass([target class])组合成为唯一的一个标识,来进行埋点记录。

  3. 除了UIViewController、UIButton空间以外,Cocoa框架的其他控件都可以通过这种方法来进行无侵入埋点。以Cocoa框架中最复杂的UITableView控件为例,你可以hook setDelegate方法来实现无侵入埋点。另外,对于Cocoa框架中的手势事件(Gesture Event),我们也可以通过hook initWithTarget:action:方法来实现无侵入埋点。

事件的唯一标识

通过运行时替换的方式,我们能够hook住所有的Objective-C方法,可以说大而全了,能够帮助我们解决大部分的埋点问题。

但是,这种方案的精确度还不够高,还无法区分相同类在不同视图树节点的情况。比如,一个视图下相同UIButton的不同实例,仅仅通过action选择器名+视图类名的组合还不能够区分开。这是,我们就需要一个唯一标识来区分不同的事件。

如何制定出这个唯一标识
  1. 可以通过视图层级的路径来解决这个问题。因为每个页面都有一个视图树结构,通过视图的superview和subviews的属性,我们就能够还原出每个页面的视图树。视图树的顶层是UIWindow,每个视图都在树的子节点上。
    一个视图下的子节点可能是同一个视图的不同实例,比如如果UIView中有两个UIButton是同一个类的不同实例,所以光靠视图树的路径还是没办法确定出唯一的视图标识。
    这时候,索引可以解决这个问题:每个视图在父视图中都会有自己的索引,如果我们再加上这个索引的话,每个视图的标识就是唯一的了。
  2. 视图层级路径加上在父视图中的索引来进行唯一标识,也无法涵盖所有情况
    类似于UITableViewCell这种具有可复用机制的视图,cell会在页面滚动时不断复用,这种情况下加索引的方法还是没用。这时候我们可以通过cell的索引,也就是indexPath的section和row的值去确定cell的唯一性。
  3. UIAlertController也比较特殊,它的特殊性在于视图层级的不固定,因为它可能出现在任何页面中。这时候我们可以通过上层视图+弹窗内容来确定唯一标识。
  4. 其它的情况,我们可以想办法找出相同视图中不同的元素,然后进行组合,最后形成一个能够区别于其他元素的标识来。
  5. 除此之外,如果在程序运行过程中,修改视图的层级和索引,比如执行insetSubView:atIndex:、removeFromSuperView等方法时,我们也无法获得唯一标识,即使只截取部分路径也无法保证后期代码更新时不会动到这个部分。就算是运行时视图层级不会修改,以后需求更新迭代页面频繁的话,视图唯一标识也需要同步的更新维护。

事件唯一标识的准确性难以保障,这也是通过运行时方法替换进行无侵入埋点很难在各个公司全面铺开的原因。虽然无侵入埋点无法覆盖到所有情况,但是还是解决了部分的埋点需求,也节省了大量的人力成本。

小结

通过运行时替换方法来实现无侵入埋点的方案,由于唯一表示难以维护和准确性难以保障的原因,很难被全面采用,一般都只是用于一些功能和视图稳定的地方,手动埋点依然占据大部分场景。

所以说,这种方案不一定是未来的方向。使用clang AST的接口,在构建时遍历AST,通过定义的规则将所需要的埋点代码直接加进入,可能会更合适。

你可能感兴趣的:(iOS 无侵入埋点的实现)