引言
随着互联网的发展,数据分析的重要性不言而喻,而数据分析依赖全面和精确的数据埋点。如果埋点不够全面或是不够精确,将导致后面的数据分析毫无意义。
1. 背景
埋点分为客户端埋点(前端埋点)和服务端埋点(后端埋点),关于后端埋点这里不做展开描述。本文介绍的 iOS 埋点属于客户端埋点,客户端埋点一般是针对用户操作进行上报,如用户的点击操作,页面路径等。传统的代码埋点对代码侵入非常大,和代码耦合非常高,随着需求的改变,代码和埋点都要进行调整。基于此,无埋点方案应用而生。
2. iOS 埋点方案介绍
iOS 埋点方案主要可以分为三种:
2.1 代码埋点
概念
代码埋点就是在代码里需要上报的地方添加上报的代码。
优点
- 开发简单。
- 可以精确的控制上报的事件和时机。
- 方便地把自定义的事件和详细的参数上报。
缺点
- 对代码侵入比较大。
- 埋点工作量大,必须是技术开发人员完成。
- 更新或新增埋点成本较高,如果遇到漏埋或者需要新埋的点,需要重新发版。
2.2 可视化埋点
概念
可视化埋点是用可视化的方式,提前把需要采集的控件事件进行圈选,并下发给客户端,用户进行操作时,埋点SDK会自动根据下发的配置进行上报,不需要进行代码埋点。
Mixpanel 提供了可视化埋点的方案,并且把代码进行了开源。SensorsData 参考了 Mixpanel,也进行了开源。本文介绍的技术方案方案参考了这两个开源框架。
优点
- 解决了代码埋点的侵入性、工作量和发版成本问题。
缺点
- 不是所有的事件都可以圈选埋点,比如一些和业务逻辑耦合比较紧的事件埋点。
- 无法在埋点时增加自定义的字段。
2.3 无埋点
概念
无埋点最早是在2013年被Heap Analytics等公司提出。无埋点就是把所有的控件操作全部由埋点SDK自动收集,所以也被称为“全埋点”。无埋点和可视化埋点非常相似,区别是可视化埋点是先进行圈选再上报,也就是只上报圈选配置的事件;无埋点是先上报用户操作,再在服务端进行圈选分析。
优点
- 在可视化埋点的基础上,可以分析已经上报的数据。也就是如果哪天突然想对某个用户操作进行分析,历史数据也可以分析。
缺点
- 不是所有的事件都可以圈选埋点,比如一些和业务逻辑耦合比较紧的事件埋点。
- 无法在埋点时增加自定义的字段。
- 此外因为上报了所有的用户的操作,相比可视化埋点网络负担稍微增大。
3. 无埋点关键技术
由于可视化埋点和无埋点核心技术点相同,可视化埋点也可以看做无埋点的一种方式。下面主要介绍几个这两种埋点方案的核心技术解决方案。
- 如何不侵入业务代码自动进行用户操作的收集。
- 事件的唯一标识(即 ID) 如何确定。
- 圈选如何实现。
3.1 如何不侵入代码自动进行收集
3.1.1 AOP
这里提到一个重要的概念 AOP,AOP 是 Aspect Oriented Programming 的缩写,意为面向切面编程。AOP 是一种编程思想,和 OOP(面向对象编程) 非常相似。AOP 是对业务处理过程中的切面进行提取,在统一的地方进行处理,可以降低各个逻辑部分之间的耦合。
我们根据下面的两张图来直观地体会下 AOP 。
从图中我们可以看出,如果我们要在不同的地方执行一些不同的操作,但都会在操作前的某一个时机做相同的动作(如用户验证,日志打印,数据上报等等)。这种情况传统的做法是每个地方都调用一遍相关接口 API。
再看上图,把验证这块整体框出来,可以形象地理解为就像一个板子,这块板子插入上面的逻辑流程。
再举个例子,iOS 里想在所有 Controller 的 ViewDidAppear 里加行日志,可以用 AOP 很好地实现。
3.1.2 Runtime Method Swizzling
介绍了 AOP 的基本概念,我们介绍下如何在 iOS 里实现 AOP 思想。这就引出了 Runtime(运行时)里的方法交换(Method Swizzling)。
我们常说 Objective-C 是一种动态语言,它总是把尽可能多的决定从编译和链接延迟到运行时执行。运行时是通过一套 C 语言 API 实现。runtime 相关的代码是开源的,感兴趣的同学可以看下相关的源码。
有两种方式可以实现方法交换:
3.1.2.1 把两个 SEL 指向的 IMP 进行交换
struct objc_method {
SEL _Nonnull method_name
char * _Nullable method_types
IMP _Nonnull method_imp
}
苹果有提供对应的 API 接口,如method_exchangeImplementations()
简单的使用方法交换会引起一些问题:
- 不是线程安全的(Method swizzling is not atomic)
- 潜在的命名冲突(Possible naming conflicts)
- 改变方法的参数(Swizzling changes the method's arguments)
- 继承问题(The order of swizzles matters)
有一些优秀的第三方开源框架在系统方法基础上提供了相对比较安全的方法交换。如RSSwizzle。
3.1.2.2 消息转发拦截
我们知道,OC 发送消息时如果无法识别一个 Selector 时,会进行消息转发服务。消息转发分为三步, 1. 方法解析处理,2. 备援接受者,3. 完整的消息转发。
Aspects 是一套经典的应用 AOP 思想的 iOS 框架,它具有良好的用户体验,比较安全,支持撤销等优点。它的总体方案是参考 KVO,通过动态生成子类,子类把原方法的 IMP 动态指向 _objc_msgForward
,当方法触发的时候,会直接进行消息转发。 通过把 forwardInvocation
指向自定义的方法,在自定义的方法里进行拦截操作处理。注意:Aspects 和 method_exchangeImplementations
同时使用会有冲突。
3.2 事件的唯一标识(即 ID) 如何确定。
要实现埋点事件的唯一性,需要对每个用户操作确定一个唯一标识,以此区分不同的事件。
下面介绍的方法和 XPath 的思路是相同的,XPath 是一门在 XML 文档中查找信息的语言,使用路径表达式来选取 XML 文档中的节点或节点集。
我们知道 APP 所有的页面可以看做一颗树的结构,对于屏幕中任何一个 view 对象,都可以得到一条唯一的从 window 到它的 path 路径。唯一标识可以由 path 路径、类名和一些属性的值来确定,称为 viewPath。示例如下:
上面这张图是一个button所在层级的描述,可以生成唯一的标识如下(使用SensorsData的结果):
UINavigationController/AutoTrackViewController/UIView/UIButton[(jjf_varE='47ee0929e87610f27936c63be82fc702a00431b4')]
这里生成 viewPath 会把 UIWindow 和系统的一些 Controller 忽略掉,减少 viewPath 的长度。还会做一些优化,如果路径中多个 view 的层级相同,会加上 view 在 superview 的 index 作为区分。
3.3 圈选如何实现。
圈选是可视化和无埋点的重要一环,主要采用客户端和 Web 端进行长连接,定期发送屏幕截图和控件树。Web 端根据屏幕截图和控件树进行展示还原。用户可在 Web 端进行圈选,并将圈选的配置下发给学生端。
3.3.1 屏幕序列化
序列化是将对象的状态信息转换为可以存储或传输的形式的过程。整个序列化过程的算法是,将 UI 对象加入到未访问队列中,从未访问队列中取一个对象访问,并序列化保存到已访问队列中。访问过程中如果对象存在子对象,则将子对象也加入到未访问队列中等待后续访问,如此递归到所有对象都已访问并序列化。
3.3.2 其他
数据传输一般是使用 WebSocket 长连接。
还有其他一些细节,如圈选配置文件解析等。
总结
没有一种埋点方案适用所有的场景,我们需要根据不同的场景挑选适合的埋点方案,代码埋点和无埋点是可以共存和相辅相成的。
End
本文参考了大量苹果官方文档和源码,以及第三方埋点框架。
下面列出部分第三方埋点框架:
- Mixpanel(开源)
- SensorsData(神策)(开源)
- Heap
- GrowingIO
- MTA(腾讯移动分析)
- 友盟
- TalkingData
- MTJ(百度移动统计)
- GoogleAnalytics(谷歌统计)
- HubbleData(网易内部使用)
- 此外,iOS无埋点数据SDK实践之路和iOS无埋点数据SDK的整体设计与技术实现这两篇文章实现讲述了业务数据无埋点的方案。主要思路是提前配置eventID和业务参数的KVC,对业务代码有一定的要求,感兴趣的可以深入学习。
本文内容为原创, 转载请注明出处~
部分图片来源于网络,如有侵权,请联系我删除~
如有错误,欢迎指正~