一、前言
本文是继 《神策分析 iOS SDK 全埋点解析之启动与退出》之后,全埋点解析系列博客的第二篇,主要介绍素与页面浏览的全埋点采集方案。在介绍具体的方案之前,我们需要先了解下相关的背景知识。
二、背景知识
2.1 Target-Action
Target-Action,也叫 “目标 - 动作” 模式,即当某个事件发生的时候,调用特定对象的特定方法。“特定对象” 就是 Target,“特定方法” 就是 Action。
例如:在 LoginViewController 页面上有一个按钮,按钮时,会调用 LoginViewController 里的 - loginBtnOnClick 方法,则 Target 是 LoginViewController, Action 是 - loginBtnOnClick 方法。
Target-Action 设计模式主要包含两个部分:
- Target(对象):接收消息的对象;
- Action(方法):用于表示需要调用的方法。
Target 可以是任意类型的对象。但是在 iOS 应用程序中,通常情况下会是一个控制器,而触发事件的对象和接收消息的对象(Target)一样,也可以是任意类型的对象。例如:手势识别器 UIGestureRecognizer 就可以在识别到手势后,将消息发送给另一个对象。
关于 Target-Action 模式,最常见的应用场景是在控件中。iOS 中的控件都是 UIControl 类或者其子类,当用户操作这些控件时,控件会将消息发送到指定的 Target,而对应的 Action 必须符合以下几种形式之一 :
- (void)doSomething;
- (void)doSomething:(id)sender;
- (void)doSomething:(id)sender forEvent:(UIEvent *)event;
- (IBAction)doSomething;
- (IBAction)doSomething:(id)sender;
- (IBAction)doSomething:(id)sender forEvent:(UIEvent *)event。
其中,以 IBAction 作为返回值类型,是为了让 Action 能在 Interface Builder 中被看到;参数 sender 就是触发事件的控件本身;参数 event 是 UIEvent 的 Target,封装了触发事件的相关信息。
我们可以通过代码或者 Interface Builder 为一个控件添加一个 Target 以及相应的 Action。
若想使用代码方式添加 Target-Action(Target-Action 可用来表示一个 Target 以及相对应的 Action),我们可以直接调用控件对象的如下方法:
- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;
我们也可以多次调用 - addTarget:action:forControlEvents: 方法给控件添加多个 Target-Action,即使多次调用 - addTarget:action:forControlEvents: 添加相同的 Target 且不同的 Action,也不会出现相互覆盖的问题。另外,在添加 Target-Action 时,Target 也可以为 nil(默认先在 self 里查找 Action)。
当我们为一个控件添加 Target-Action 后,控件又是如何找到 Target 对象并执行对应的 Action 呢?
在 UIControl 类中有一个方法:
- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;
用户操作控件(例如)时,首先会调用这个方法,并将事件转发给应用程序的 UIApplication 对象。
同时,在 UIApplication 类中也有一个类似的实例方法:
- (BOOL)sendAction:(SEL)action to:(nullable id)target from:(nullable id)sender forEvent:(nullable UIEvent *)event;
如果 Target 对象不为 nil,应用程序会让该对象调用对应的方法响应事件;如果 Target 为 nil,应用程序会在响应者链中搜索定义了该方法的对象,然后执行该方法。
基于 Target-Action 设计模式,我们可以实现 $AppClick 事件的全埋点。
2.2 Method Swizzling
Method Swizzling,顾名思义,就是交换两个方法的实现。简单来说,就是利用 Objective-C runtime 的动态绑定特性,将一个方法的实现与另一个方法的实现进行交换。
在 Objective-C 的 runtime 中,一个类是用一个名为 objc_class 的结构体表示的,它的定义如下:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
在上面的结构体中,虽然有很多字段在 OBJC2 中已经废弃了(OBJC2_UNAVAILABLE),但是了解这个结构体有助于我们理解 Method Swizzling 的底层原理。从上述结构体中可以发现,有一个 objc_method_list 指针,它保存着当前类的所有方法列表。
同时,objc_method_list 也是一个结构体,它的定义如下:
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
在上面的结构体中,有一个 objc_method 字段,它的定义如下:
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
从上面的结构体中可以看出,一个方法由下面三个部分组成:
- meth
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/101393.html