关于前端:神策分析-iOS-SDK-全埋点解析之元素点击与页面浏览

40次阅读

共计 16325 个字符,预计需要花费 41 分钟才能阅读完成。

前言

本文是继《神策剖析 iOS SDK 全埋点解析之启动与退出》之后,全埋点解析系列博客的第二篇,次要介绍元素点击与页面浏览的全埋点采集计划。在介绍具体的计划之前,咱们须要先理解下相干的背景常识。
背景常识

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)sendAction:(SEL)action to:(nullableid)target forEvent:(nullable UIEvent *)event;
    咱们也能够屡次调用 – 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:(nullableid)target forEvent:(nullable UIEvent *)event;
    用户操作控件(例如点击)时,首先会调用这个办法,并将事件转发给应用程序的 UIApplication 对象。
    同时,在 UIApplication 类中也有一个相似的实例办法:
  • (BOOL)sendAction:(SEL)action to:(nullableid)target from:(nullableid)sender forEvent:(nullable UIEvent *)event;
    如果 Target 对象不为 nil,应用程序会让该对象调用对应的办法响应事件;如果 Target 为 nil,应用程序会在响应者链中搜寻定义了该办法的对象,而后执行该办法。
    基于 Target-Action 设计模式,咱们能够实现 $AppClick 事件的全埋点。
    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 {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp 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;
}
从下面的构造体中能够看出,一个办法由上面三个局部组成:
method_name:办法名;
method_types:办法类型;
method_imp:办法实现。
应用 Method Swizzling 替换办法,其实就是批改了 objc_method 构造体中的 method_imp,也即扭转了 method_name 和 method_imp 的映射关系:原有的 SEL(A)-IMP(A)、SEL(B)-IMP(B) 对应关系变成 SEL(A)-IMP(B)、SEL(B)-IMP(A)。如图 2-1 所示:


图 2-1 Method Swizzling 前后的映射关系

响应者链

家喻户晓,UIResponder 类是 iOS 应用程序中专门用来响应用户操作事件的,例如:
Touch Events:即触摸事件;
Motion Events:即静止事件;
Remote Control Events:即近程管制事件。
因为 UIApplication、UIViewController、UIView 类都是 UIResponder 的子类,所以它们都具备响应以上事件的能力。另外,自定义的 UIView 和自定义视图控制器也都能够响应以上事件。在 iOS 应用程序中,UIApplication、UIViewController、UIView 类的对象也都是一个个响应者,这些响应者会造成一个响应者链。一个残缺的响应者链传递规定(程序)如下:UIView → UIViewController → RootViewController → Window → UIApplication → UIApplicationDelegate,如图 2-2 所示:

图 2-2 事件响应者链(图片来源于 Apple 开发者文档)

点击事件

元素点击

计划简介

通过 Target-Action 执行模式可知,在执行 Action 办法之前,会先后通过控件和 UIApplication 对象发送事件相干的信息。因而,咱们能够通过 Method Swizzling 替换 UIApplication 的 – sendAction:to:from:forEvent: 办法,而后在替换后的办法中触发 $AppClick 事件,并依据 target 和 sender 采集相干的属性,即可实现 $AppClick 事件的全埋点。
对于 UIApplication 类中的 – sendAction:to:from:forEvent: 办法,咱们以给 UIButton 设置 action 为例介绍如下:
[button addTarget:person action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
参数:
action:Action 办法对应的 selector,即示例中的 btnAction;
target:Target 对象,即示例中的 person。如果 Target 为 nil,应用程序会将音讯发送给第一个响应者,并从第一个响应者沿着响应链向上发送音讯,直到音讯被解决为止;
sender:被用户点击或拖动的控件(发送 Action 音讯的对象),即示例中的 button;
event:UIEvent 对象,它封装了触发事件的相干信息。
返回值:
如果有 responder 对象解决了此音讯,返回 YES,否则返回 NO。

具体实现

上面咱们具体介绍如何通过 Method Swizzling 替换 UIApplication 的 – sendAction:to:from:forEvent: 办法来实现 $AppClick 事件的全埋点。

  1. 在 SDK 初始化时替换 – sendAction:to:from:forEvent: 办法:
  • (void)_enableAutoTrack {… NSError *error = NULL; //$AppClick // Actions & Events [UIApplication sensorsdata_swizzleMethod:@selector(sendAction:to:from:forEvent:) withMethod:@selector(sensorsdata_sendAction:to:from:forEvent:) error:&error];…}
  1. 在替换的办法里做埋点操作:

// action: 须要调用的办法 // to: 接管音讯的对象 // from: 须要传递动作音讯的参数对象 - (BOOL)sensorsdata_sendAction:(SEL)action to:(id)to from:(id)from forEvent:(UIEvent *)event {// 触发点击事件 [self sa_sendAction:action to:to from:from forEvent:event]; return YES;}

至此,一个简略的 $AppClick 事件的全埋点就实现了。

计划优化

通过 Method Swizzling 咱们实现了简略的 $AppClick 事件的全埋点。然而,仅仅采集一个点击动作并不能满足理论的业务需要,还须要采集与控件相干的信息。
个别状况下,对于一个控件的点击事件,咱们至多还须要采集如下信息(属性):
控件类型($element_type);
控件上显示的文本($element_content);
控件所属页面,即 UIViewController($screen_name)。
基于目前的计划,咱们来看如何实现采集上述三个属性。

  1. 获取控件类型。获取控件类型绝对比较简单,咱们能够间接应用控件的 class 名称来代表以后控件的类型。获取控件的 class 名称可用如下形式:

NSString *elementType = NSStringFromClass([sender class]);

  1. 获取控件上显示的文本。因为个别实现点击的控件都继承于 UIView,因而咱们创立一个 UIView 的分类并且实现获取显示内容的办法:
  • (NSString )sensorsdata_elementContent {… NSMutableArray<NSString *> elementContentArray = [NSMutableArray array]; for (UIView subview in self.subviews) {// 疏忽暗藏控件 if (subview.isHidden || subview.sensorsAnalyticsIgnoreView) {continue;} NSString temp = subview.sensorsdata_elementContent; if (temp.length > 0) {[elementContentArray addObject:temp]; } } if (elementContentArray.count > 0) {[elementContent appendString:[elementContentArray componentsJoinedByString:@”-“]]; } return elementContent.length == 0 ? nil : [elementContent copy];}

咱们晓得,可点击的控件有各种类型。因而,咱们须要先判断控件的类型,再依据类型来获取对应的显示内容。上面以 UIButton 为例,获取显示内容:

  • (NSString )sensorsdata_elementContent {NSString text = self.titleLabel.text; if (!text) {text = super.sensorsdata_elementContent;} return text;}
  1. 获取控件所属页面。如何晓得一个 UIView 属于哪个 UIViewController?这就须要借助 UIResponder 了。通过响应者链能够晓得,对于任意一个视图来说,都能通过响应者链找到它所在的视图控制器,也就是其所属的页面,从而达到获取所属页面信息的目标:
  • (UIViewController )sensorsdata_viewController {… UIResponder response = self; while ((response = [response nextResponder])) {if ([response isKindOfClass:[UIViewController class]]) {return (UIViewController *)response; } } return nil;…}

至此,$AppClick 事件的全埋点计划曾经能够反对获取控件相干的信息了。
UITableView 和 UICollectionView 点击

计划简介

上一节中咱们介绍了通过 Target-Action 形式采集 $AppClick 事件的全埋点,不过还存在两个比拟非凡的控件:UITableView 和 UICollectionView。
这两个控件的点击个别是指采集 UITableViewCell 和 UICollectionViewCell 的点击事件,而 UITableViewCell 和 UICollectionViewCell 都是间接继承自 UIView 类,而不是 UIControl 类。因而,上节提到的 $AppClick 事件的全埋点计划并不实用。
咱们晓得,UITableView 和 UICollectionView 实现点击的代理办法别离为 – tableView:didSelectRowAtIndexPath: 和 – collectionView:didSelectItemAtIndexPath:。因而,能够通过 Method Swizzling 的形式来替换办法的实现,从而实现 UITableView 和 UICollectionView 控件的 $AppClick 事件的全埋点采集。

具体实现

因为 UITableView 和 UICollectionView 实现 $AppClick 事件的全埋点计划相似,这里以 UITableView 为例,来介绍如何实现 $AppClick 事件的全埋点。

  1. 替换 UITableVIew 的 – setDelegate: 办法:
  • load {… [UITableVIew sensorsdata_swizzleMethod:@selector(setDelegate:) withMethod:@selector(sensorsdata_setDelegate:)];…}
  1. 在 – sensorsdata_setDelegate: 办法中,咱们能够通过参数 delegate 来获取实现 UITableViewDelegate 协定的对象。而后,替换该对象中的 – tableVIew:didSelectRowAtIndexPath: 办法即可:

static void sensorsdata_tablViewDidSelectRowAtIndexPath(id self, SEL _cmd, id tableView, id indexPath) {SEL selector = NSSelectorFromString(@”sensorsdata_tableView:didSelectRowAtIndexPath:”); // 调用原始的 -tableVIew:didSelectRowAtIndexPath: 办法实现 ((void(*)(id, SEL, id, id))objc_msgSend)(self, selector, tableView, indexPath); // 采集 $AppClick 事件 }

  1. 因为 UITableView 的 delegate 对象是在运行时动静设置的,该对象具备不确定性。因而,咱们须要动静地对 delegate 对象增加须要替换的办法,能力与 – tableView:didSelectRowAtIndexPath: 办法进行替换:

static void sensorsdata_setDelegate(id obj , SEL sel, id delegate) {… Class class = [delegate class]; SEL swizSel = NSSelectorFromString(@”sensorsdata_tableView:didSelectRowAtIndexPath:”); Method originalMethod = class_getInstanceMethod(class, @selector(tableView:didSelectRowAtIndexPath:)); Method swizzledMethod = class_getInstanceMethod(class, swizSel); method_exchangeImplementations(originalMethod, swizzledMethod);…}

至此,咱们曾经实现了 UITableView 控件的 $AppClick 事件的全埋点采集。UICollectionView 的 $AppClick 事件的全埋点采集整体上与 UITableView 相似,不同之处是替换的办法须要换成 UICollectionView 对应的代理办法 – collectionView:didSelectItemAtIndexPath:,这里不再赘述了。

计划优化

目前为止咱们曾经实现了 UITableView 和 UICollectionView 的 $AppClick 事件的全埋点采集。同样的,仅仅采集一个点击动作并不能满足理论的业务需要,还须要采集与控件相干的信息。
对于 UITableView 和 UICollectionView 而言,通常须要采集被点击 cell 的显示内容和所在位置。上面以 UITableView 为例,介绍如何采集 cell 的显示内容和所在位置。

  1. 获取点击的 UITableViewCell 对象:

UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];

  1. 因为 UITableViewCell 是一个简单的控件,可能是由泛滥的元素组合而成。因而,咱们须要遍历 UITableViewCell 上的所有元素,而后把获取的元素内容依照肯定规定进行拼接:
  • (NSString )sensorsdata_elementContent {… NSMutableArray<NSString *> elementContentArray = [NSMutableArray array]; for (UIView subview in self.subviews) {// 疏忽暗藏控件 if (subview.isHidden || subview.sensorsensorsdatanalyticsIgnoreView) {continue;} NSString temp = subview.sensorsdata_elementContent; if (temp.length > 0) {[elementContentArray addObject:temp]; } } if (elementContentArray.count > 0) {[elementContent appendString:[elementContentArray componentsJoinedByString:@”-“]]; } } return elementContent.length == 0 ? nil : [elementContent copy];}
  1. 至此,咱们曾经找到了 UITableViewCell 上所有非暗藏元素的显示内容。接下来,咱们须要获取 UITableViewCell 的地位。因为曾经获取到 indexPath 参数,所以间接依照规定拼接即可:
  • (NSString )sensorsdata_elementPositionWithIndexPath:(NSIndexPath )indexPath {return [NSString stringWithFormat: @”%ld:%ld”, (long)indexPath.section, (long)indexPath.row];}

至此,UITableView 的 $AppClick 事件的全埋点计划曾经能够反对获取 cell 的相干信息了。UICollectionView 也能够采纳同样的计划获取 cell 的相干信息,这里不再赘述了。

手势采集

在平时的开发过程中,零碎提供的可点击控件往往不能满足咱们简单的需要,因而常常须要对 UIView 增加手势来实现视图的点击。
苹果公司为了升高开发者在手势事件处理方面的开发难度,定义了一个抽象类 UIGestureRecognizer 来帮助开发者。UIGestureRecognizer 是具体手势识别器的形象基类,它定义了一组手势识别器常见行为,还反对通过设置委托(即实现了 UIGestureRecognizerDelegate 协定的对象),对某些行为进行更细粒度的定制。
手势识别器必须被增加在一个特定的视图上(例如:UILabel、UIImageView 等控件),这须要通过调用 UIView 类中的 – addGestureRecognizer: 办法进行增加。手势识别器也是用了 Target-Action 设计模式。当咱们为一个手势识别器增加一个或者多个 Target-Action 后,在视图上进行触摸操作时,一旦零碎辨认了该手势,就会向所有的 Target(对象)发送音讯,并执行 Action(办法)。尽管手势识别器和 UIControl 类一样,都是应用了 Target-Action 设计模式,然而手势识别器并不会将音讯交由 UIApplication 对象来进行发送。因而,咱们无奈应用与 UIControl 控件雷同的解决形式,即无奈通过响应者链的形式来实现对手势操作的全埋点。
因为 UIGestureRecognizer 是一个形象基类,所以它并不会解决具体的手势。因而,对于轻拍(UITapGestureRecognizer)、长按(UILongPressGestureRecognizer)等具体的手势触摸事件,须要应用相应的子类(即具体的手势识别器)进行解决。
常见的具体手势识别器有:
UITapGestureRecognizer:轻拍手势;
UILongPressGestureRecognizer:长按手势;
UIPinchGestureRecognizer:捏合(缩放)手势;
UIRotationGestureRecognizer:旋转手势;
UISwipeGestureRecognizer:轻扫手势;
UIPanGestureRecognizer:平移手势;
UIScreenEdgePanGestureRecognizer:屏幕边缘平移手势。

计划简介

通过上节的介绍能够晓得,常见的具体手势识别器有很多种。不过,给所有的具体手势识别器增加 Target-Action 的办法都是雷同的,常见的次要是通过以下的两个办法进行增加:

  • initWithTarget:action:;
  • addTarget:action。
    因而,咱们能够在增加一个新的 Target-Action 的办法时通过 Method Swizzling 来实现手势全埋点采集。

    具体实现

在理论的开发过程中,应用比拟多的是 UITapGestureRecognizer 和 UILongPressGestureRecognizer 手势识别器,它们别离用来解决轻拍手势和长按手势。上面咱们以这两种手势为例,来介绍如何实现手势全埋点采集。

  1. 在初始化 SDK 时替换 – initWithTarget:action: 和 – addTarget:action: 办法:
  • (SensorsAnalyticsSDK )sharedInstanceWithConfig:(nonnull SAConfigOptions )configOptions {… [UITapGestureRecognizer sensorsdata_swizzleMethod:@selector(initWithTarget:action:) withMethod:@selector(sensorsdata_initWithTarget:action:) error:&error]; [UITapGestureRecognizer sensorsdata_swizzleMethod:@selector(addTarget:action:) withMethod:@selector(sensorsdata_addTarget:action:)]; [UILongPressGestureRecognizer sa_swizzleMethod:@selector(addTarget:action:) withMethod:@selector(sa_addTarget:action:) error:&error]; [UILongPressGestureRecognizer sa_swizzleMethod:@selector(initWithTarget:action:) withMethod:@selector(sa_initWithTarget:action:) error:&error];…}
  1. 在替换后的办法里增加一个新的 Target-Action 即可实现 UITapGestureRecognizer 和 UILongPressGestureRecognizer 手势的采集:
  • (instancetype)sensorsdata_initWithTarget:(id)target action:(SEL)action {[self sensorsdata_initWithTarget:target action:action]; // 因为办法曾经替换,所以这里是调用 sensorsdata_addTarget:action: 的实现办法 [self addTarget:target action:action]; return self;} – (void)sensorsdata_addTarget:(id)target action:(SEL)action {[self sensorsdata_addTarget:self action:@selector(trackGestureRecognizerAppClick:)]; [self sensorsdata_addTarget:target action:action];}
  1. 在 – trackGestureRecognizerAppClick: 办法中能够实现 $AppClick 事件的采集:
  • (void)trackGestureRecognizerAppClick:(UIGestureRecognizer )gesture {… UIView view = gesture.view; // 神策暂定只采集 UILable 和 UIImageView BOOL isTrackClass = [view isKindOfClass:UILabel.class] || [view isKindOfClass:UIImageView.class]; if (!isTrackClass) {return;} // 触发 $AppClick 事件 }

计划优化

咱们晓得,对于任何一个手势,其实都有不同的状态,例如:
UIGestureRecognizerStateBegan;
UIGestureRecognizerStateChanged;
UIGestureRecognizerStateEnded;
UIGestureRecognizerStateCancelled。
上述不同的状态均会触发 Action。因而,目前的计划会造成屡次采集 $AppClick 事件。如何解决这个问题呢?因为在触发 Action 的时候能够获取到手势的状态,因而只须要在手势的状态为 UIGestureRecognizerStateEnded 时触发 $AppClick 事件即可解决该问题:

  • (void)trackGestureRecognizerAppClick:(UIGestureRecognizer *)gesture {// 手势处于 Ended 状态 if (gesture.state != UIGestureRecognizerStateEnded) {return;} … // 触发 $AppClick 事件 }

至此,咱们就实现了轻拍和长按手势事件的全埋点计划。对于其余手势事件的全埋点,实现思路都是雷同的,这里不再赘述了。

页面浏览事件

家喻户晓,每一个 UIViewController 都治理着一个由多个视图组成的树形构造,其中根视图保留在 UIViewController 的 view 属性中。UIViewController 会懒加载它所治理的视图集,直到第一次拜访 view 属性时,才会去加载或者创立 UIViewController 的视图集。
几种罕用的加载或者创立 UIViewController 的视图集的办法如下:
应用 Storyboard;
应用 Nib 文件;
应用代码,即重写 – loadView。
以上这些办法最终都会创立出适合的根视图并保留在 UIViewController 的 view 属性中,这是 UIViewController 生命周期的第一步。当 UIViewController 的根视图须要展现在页面上时,会调用 – viewDidLoad 办法。在这个办法中,咱们能够做一些对象初始化相干的工作。
须要留神的是:此时视图的 bounds 还没有确定。如果应用代码创立视图,- viewDidLoad 办法会在 – loadView 办法调用完结之后运行;如果应用 Storyboard 或者 Nib 文件创建视图,- viewDidLoad 办法则会在 – awakeFromNib 办法之后调用。
当 UIViewController 的视图在屏幕上的显示状态发生变化时,UIViewController 会主动回调一些办法,确保子类可能响应到这些变动。图 4-1 展现了 UIViewController 在不同的显示状态时回调不同的办法:

图 4-1 UIViewController 不同状态下的办法调用(图片来源于 Apple 开发者文档)
在 UIViewController 被销毁之前,还会回调 – dealloc 办法,咱们个别通过重写这个办法来被动开释不能被 ARC 主动开释的资源。
计划简介

咱们当初对 UIViewController 的整个生命周期有了一些根本理解。那么如何实现页面浏览事件($AppViewScreen)的全埋点呢?
通过 UIViewController 的整个生命周期可知,当执行到 – viewDidAppear: 办法时,示意视图曾经在屏幕上渲染实现,即页面曾经显示进去,正等待用户进行下一步操作。因而,执行到 – viewDidAppear: 办法的工夫点是触发页面浏览事件的最佳时机。
如果想要实现页面浏览事件的全埋点,能够通过 Method Swizzling 来替换 – viewDidAppear: 办法,而后在替换后的办法里采集 $AppViewScreen 即可。

具体实现

  1. 在初始化 SDK 时替换 – viewDidAppear: 办法:
  • (SensorsAnalyticsSDK )sharedInstanceWithConfig:(nonnull SAConfigOptions )configOptions {… [UIViewController sensorsdata_swizzleMethod:@selector(viewDidAppear:) withMethod:@selector(sensorsdata_viewDidAppear:)];…}
  1. 在 UIViewController 的分类中实现 – sensorsdata_viewDidAppear: 办法:
  • (void)sensorsdata_viewDidAppear:(BOOL)animated {[self sensorsdata_viewDidAppear:animated]; // 触发 $AppViewScreen}
  1. 因为这种形式采集到的页面浏览事件可能存在一些咱们增加的 childViewController,通常状况下咱们并不需要这些页面的浏览事件。因而,默认状况下禁止采集 childViewController 的页面浏览事件,并提供应用预编译宏的形式关上采集 childViewController 的页面浏览事件:
  • (void)sensorsdata_viewDidAppear:(BOOL)animated {SensorsAnalyticsSDK instance = [SensorsAnalyticsSDK sharedInstance]; #ifndef SENSORS_ANALYTICS_ENABLE_AUTOTRACK_CHILD_VIEWSCREEN UIViewController viewController = (UIViewController *)self; if (![viewController.parentViewController isKindOfClass:[UIViewController class]] || [viewController.parentViewController isKindOfClass:[UITabBarController class]] || [viewController.parentViewController isKindOfClass:[UINavigationController class]] || [viewController.parentViewController isKindOfClass:[UIPageViewController class]] || [viewController.parentViewController isKindOfClass:[UISplitViewController class]]) {// 触发 $AppViewScreen [instance autoTrackViewScreen:viewController]; }#else // 触发 $AppViewScreen [instance autoTrackViewScreen:self];#endif } [self sensorsdata_viewDidAppear:animated]; }

计划优化

因为上述计划替换了所有 UIViewController 的 – viewDidAppear: 办法,因而会采集到很多咱们并不需要的零碎页面。为了解决这个问题,咱们引入了黑名单机制:把须要疏忽的页面寄存在一个 json 文件里,在触发 $AppViewScreen 之前对黑名单里的 UIViewController 进行过滤:

  • (void)trackViewScreen:(UIViewController )controller properties:(nullable NSDictionary<NSString *, id> )properties autoTrack:(BOOL)autoTrack {// 判断以后 UIViewController 是否在黑名单内 if ([self isBlackListViewController:controller ofType:SensorsensorsdatanalyticsEventTypeAppViewScreen]) {return;} // 触发 $AppViewScreen}

在理论测试过程中,咱们发现通过手势侧滑返回时会触发屡次页面浏览事件。而造成这种景象的起因是:通过手势侧滑返回的过程中会屡次触发 – viewDidAppear:。因而,咱们还须要减少判断来解决这个问题:

  • (void)sensorsdata_viewDidAppear:(BOOL)animated {if (instance.previousTrackViewController != self) {// 触发 $AppViewScreen} if (instance.previousTrackViewController != self && UIApplication.sharedApplication.keyWindow == self.view.window) {instance.previousTrackViewController = self;} [self sensorsdata_viewDidAppear:animated];}

至此,咱们就实现了页面浏览事件($AppViewScreen)的全埋点。

总结

本文次要介绍了神策剖析 iOS SDK 的元素点击与页面浏览的全埋点采集计划,具体的实现能够参考 iOS SDK 源码。
在全埋点采集计划中大量应用了 Method Swizzling,这种形式的长处如下:
Method Swizzling 属于成熟的技术,绝对比较稳定;
性能相对来说也比拟高。
然而,毛病也不言而喻:
对原始代码有入侵,容易造成抵触;
一旦呈现问题,影响的范畴较大。
因而,欢送大家在开源社区一起探讨更好的解决方案。

文章起源:神策技术社区

正文完
 0