写点什么

神策分析 iOS SDK 全埋点解析之元素点击与页面浏览

发布于: 4 小时前
神策分析 iOS SDK 全埋点解析之元素点击与页面浏览

前言

本文是继 《神策分析 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 事件的全埋点 。对于 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 我们实现了简单的 element_type);控件上显示的文本(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 实现点击的代理方法分别为 - tableView:didSelectRowAtIndexPath: 和 - collectionView:didSelectItemAtIndexPath:。因此,可以通过 Method Swizzling 的方式来交换方法的实现,从而实现 UITableView 和 UICollectionView 控件的 $AppClick 事件的全埋点采集。具体实现


由于 UITableView 和 UICollectionView 实现 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 事件的全埋点采集整体上与 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 事件即可解决该问题:


  • (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 即可。具体实现


  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 属于成熟的技术,相对比较稳定;性能相对来说也比较高。但是,缺点也显而易见:对原始代码有入侵,容易造成冲突;一旦出现问题,影响的范围较大。因此,欢迎大家在开源社区一起讨论更好的解决方案。


文章来源:神策技术社区

发布于: 4 小时前阅读数: 3
用户头像

公众号:神策技术社区 2021.03.09 加入

公众号:神策技术社区

评论

发布
暂无评论
神策分析 iOS SDK 全埋点解析之元素点击与页面浏览