iOS全埋点方案中,界面预览事件如何实现?

2026-05-19 18:011阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2901个文字,预计阅读时间需要12分钟。

iOS全埋点方案中,界面预览事件如何实现?

前言:我们先了解 UIViewController 的生命周期相关内容,以及 iOS 的黑魔法 Method Swizzling。然后,再理解页面浏览事件($AppViewScreen)全埋点的实现原理。

一、UIViewController 生命周期

1. viewDidLoad:视图加载完成时调用。

2.viewWillAppear:视图即将出现在屏幕上时调用。

3.viewDidAppear:视图已经出现在屏幕上时调用。

4.viewWillDisappear:视图即将从屏幕消失时调用。

5.viewDidDisappear:视图已经从屏幕消失时调用。

二、Method Swizzling

Method Swizzling 是一种动态修改方法调用的技巧,可以用来在运行时替换或增强方法的行为。

三、页面浏览事件全埋点实现原理

页面浏览事件全埋点是通过 Method Swizzling 技术实现的,具体步骤如下:

1. 在 UIViewController 的 viewDidLoad 方法中,使用 Method Swizzling 替换 viewDidLoad 方法,在替换后的方法中添加埋点逻辑。

2.在 viewWillAppear 方法中,使用 Method Swizzling 替换 viewWillAppear 方法,在替换后的方法中添加埋点逻辑。

3.在 viewDidAppear 方法中,使用 Method Swizzling 替换 viewDidAppear 方法,在替换后的方法中添加埋点逻辑。

4.在 viewWillDisappear 方法中,使用 Method Swizzling 替换 viewWillDisappear 方法,在替换后的方法中添加埋点逻辑。

5.在 viewDidDisappear 方法中,使用 Method Swizzling 替换 viewDidDisappear 方法,在替换后的方法中添加埋点逻辑。

通过以上步骤,我们可以实现对页面浏览事件的全面监控和埋点。

前言

​ 我们先了解 UIViewController 生命周期相关的内容和 iOS 的“黑魔法” Method Swizzling。然后再了解页面浏览事件($AppViewScreen)全埋点的实现原理

一、UIViewController 生命周期

​ 众所周知,每一个 UIViewController 都管理着一个由多个视图组成的树形结构,其中根视图保存在 UIViewController 的 view 属性中。UIViewController 会懒加载它所管理的视图集,直到第一次访问 view 属性时,才会去加载或者创建 UIViewController 的视图集。

有以下几种常用的方式加载或者创建 UIViewController 的视图集:

  • 使用 Storyboard
  • 使用 Nib 文件
  • 使用代码,即重写 - loadView

​ 以上这些方法,最终都会创建出合适的根视图并保存在 UIViewController 的 view 属性中,这是 UIViewController 生命周期的第一步。当 UIViewController 的根视图需要展示在页面上时,会调用 - viewDidLoad 方法。在这个方法中,我们可以做一些对象初始化相关的工作。

​ 需要注意的是:此时,视图的 bounds 还没有确定。对于使用代码创建视图,- viewDidLoad 方法会在 -loadView 方法调用结束之后运行;如果使用的是 Stroyboard 或者 Nib 文件创建视图,- viewDidLoad 方法则会在 - awakeFromNib 方法之后调用。

​ 当 UIViewController 的视图在屏幕上的显示状态发生变化时,UIViewController 会自动回调一些方法,确保子类能够响应到这些变化。如下图所示,它展示了 UIViewController 在不同的显示状态时会回调不同的方法。

​ 在 UIViewController 被销毁之前,还会回调 - dealloc 方法,我们一般通过重写这个方法来主动释放不能被 ARC 自动释放的资源。

​ 我们现在对 UIViewController 的整个生命周期有了一些基本了解。那么,我们如何去实现页面浏览事件( $AppViewScreen 事件)的全埋点呢?

​ 通过 UIViewController 的生命周期可知,当执行到 - viewDidAppear: 方法时,表示视图已经在屏幕上渲染完成,也即页面已经显示出来了,正等待用户进行下一步操作。因此,- viewDidAppear: 方法就是我们触发页面浏览事件的最佳时机。如果想要实现页面浏览事件的全埋点,需要使用 iOS 的“黑魔法” Method Swizzling 相关的技术。

二、Method Swizzling 黑魔法

​ Method Swizzling,顾名思义,就是交换两个方法的实现。简单的来说,就是利用 Objective-C runtime 的动态绑定特性,把一个方法的实现与另一个方法的实现进行交换。

2.1 Method Swizzling 基础

​ 在 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 字段,我们再来看看 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 的映射关系,如下图所示。

那我们如何改变 method_name 和 method_imp 的映射关系呢?在 Objective-C 的 runtime 中,提供了很多非常方便使用的函数,让我们可以很简单的就能实现 Method Swizzling,即改变 method_name 和 method_imp 的映射关系,从而达到交换方法的效果。

iOS全埋点方案中,界面预览事件如何实现?

2.2 实现 Method Swizzling 的相关函数
  1. Method class_getInstanceMethod

    // 返回目标类 aClass、方法名为 aSelector 的实例方法 // aClass :目标类 // aSelector: 方法名 OBJC_EXPORT Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

  2. BOOL class_addMethod

    // 给目标类 aClass 添加一个新的方法,同时包括方法的实现 // aClass: 目标类 // aSelector: 要添加方法的方法名 // imp: 要添加方法的方法实现 // types: 方法实现的编码类型 OBJC_EXPORT BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

  3. IMP method_getImplementation

    // 返回方法实现的指针 // 目标方法 OBJC_EXPORT IMP _Nonnull method_getImplementation(Method _Nonnull m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

  4. IMP class_replaceMethod

    // 替换目标类 aClass 的 aSelector 方法指针 // aClass: 目标类 // aSelector: 目前方法的方法名 // imp:新方法的方法实现 // types: 方法实现的编码类型 OBJC_EXPORT IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

  5. void method_exchangeImplementations

    // 交换2个方法的实现指针 // m1: 交换方法1 // m2: 交换方法2 OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

2.3 实现 Method Swizzling

第一步 创建 NSObject 的分类 NSObject+SASwizzler

第二步 在 NSObject+SASwizzler.h 声明方法交换方法

/// 交换方法名为 originalSEL 和方法名为 alternateSEL 两个方法实现 /// @param originalSEL 原始的方法名称 /// @param alternateSEL 要交换的方法名称 + (BOOL)sensorsdata_swizzleMethod:(SEL)originalSEL withMethod:(SEL)alternateSEL;

第三步 在 NSObject+SASwizzler.m 实现方法的交换

+ (BOOL)sensorsdata_swizzleMethod:(SEL)originalSEL withMethod:(SEL)alternateSEL { // 获取原始方法 Method originalMethod = class_getInstanceMethod(self, originalSEL); // 当原始的方法不存在时,返回NO,表示 Swizzler 失败 if (!originalMethod) { return NO; } // 获取要交换的方法 Method alternateMethod = class_getInstanceMethod(self, alternateSEL); // 当交换的方法不存在时,返回NO,表示 Swizzler 失败 if (!alternateMethod) { return NO; } // 交换两个方法的实现 method_exchangeImplementations(originalMethod, alternateMethod); return YES; } 三、实现界面预览事件全埋点

​ 利用方法交换,来交换 UIViewController 的 -viewDidAppear: 方法,然后在方法交换中触发 $AppViewScreen 事件,来实现界面预览的全埋点。

3.1 实现步骤

第一步:在 SensorsSDK 项目中,新增一个 UIViewController 类别 UIViewController+SensorsData

第二步:在 UIViewController+SensorsData.m 类别新增交换方法 - sensorsdata_viewDidAppear:,然后再交换方法中调用原始方法,并触发 $AppViewScreen 事件

- (void)sensorsdata_viewDidAppear:(BOOL)animated { // 调用原始方法, 即 - viewDidAppear [self sensorsdata_viewDidAppear:animated]; // 触发 $AppViewScreen 事件 NSMutableDictionary *properties = [NSMutableDictionary dictionary]; [properties setValue:NSStringFromClass([self class]) forKey:@"$screen_name"]; [properties setValue:self.navigationItem.title forKey:@"$title"]; [[SensorsAnalyticsSDK sharedInstance] track:@"$AppViewScreen" properties:properties]; }

第三步: 在 UIViewController+SensorsData.m 中重写 + load 类方法,并在 + load 类方法中调用 NSObject+SASwizzler 的类方法交换

+ (void)load { [UIViewController sensorsdata_swizzleMethod:@selector(viewDidAppear:) withMethod:@selector(sensorsdata_viewDidAppear:)]; }

第四步 : 测试验证

{ "event" : "$AppViewScreen", "time" : 1648626597682, "propeerties" : { "$model" : "x86_64", "$manufacturer" : "Apple", "$lib_version" : "1.0.0", "$os" : "iOS", "$app_version" : "1.0", "$screen_name" : "ViewController", "$os_version" : "15.2", "$lib" : "iOS" } } 3.2 优化

问题:在应用程序启动过程中,会触发多余的 $AppViewScreen ,我们可以引入黑名单的机制,即在黑名单里配置那些 UIViewController 及子类不触发 $AppViewScreen 事件。

第一步 创建一个 sensorsdata_black_list.plist 文件,并把 root 类型改成 Array,该文件就是黑名单文件,然后在黑名单文件中添加控制器,如图所示:

第二步 在 UIViewController+SensorsData.m 文件中新增 - shouldTrackAppViewScreen 方法,用来判断当前控制器是否在黑名单中。

static NSString * const kSensorsDataBlackListFileName = @"sensorsdata_black_list"; // 黑名单 - (BOOL)shouldTrackAppViewScreen { static NSSet *blackList = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *path = [[NSBundle bundleForClass:SensorsAnalyticsSDK.class] pathForResource:kSensorsDataBlackListFileName ofType:@"plist"]; NSArray *classNames = [NSArray arrayWithContentsOfFile:path]; NSMutableSet *set = [NSMutableSet setWithCapacity:classNames.count]; for (NSString *className in classNames) { [set addObject:NSClassFromString(className)]; } blackList = [set copy]; }); for (Class cla in blackList) { if ([self isKindOfClass:cla]) { return NO; } } return YES; }

第三步 在触发 $AppViewScreen 事件之前,判断是否在黑名单中

- (void)sensorsdata_viewDidAppear:(BOOL)animated { // 调用原始方法, 即 - viewDidAppear [self sensorsdata_viewDidAppear:animated]; // 触发 $AppViewScreen 事件 if ([self shouldTrackAppViewScreen]) { NSMutableDictionary *properties = [NSMutableDictionary dictionary]; [properties setValue:NSStringFromClass([self class]) forKey:@"$screen_name"]; [properties setValue:self.navigationItem.title forKey:@"$title"]; [[SensorsAnalyticsSDK sharedInstance] track:@"$AppViewScreen" properties:properties]; } }

第四步 测试验证

​ 运行Demo,所添加到黑名单中的 controller 不会发送 $AppViewScreen 事件。

3.4 遗留问题

​ 按照目前的方案实现 $AppViewScreen 事件的全埋点,会有2个问题:

应用程序热启动是(从后台恢复),第一个界面没有触发 $AppViewScreen 事件。原因是这个界面没有再次执行 - viewDidAppear: 方法

要求 UIViewController 的子类不重写 -viewDidAppear:方法,一旦重写,必须调用[super viewDidAppear:animated], 否则不会触发 $AppViewScreen 事件。原因是直接交换了 UIViewController 的 - viewDidAppear: 方法。

本文共计2901个文字,预计阅读时间需要12分钟。

iOS全埋点方案中,界面预览事件如何实现?

前言:我们先了解 UIViewController 的生命周期相关内容,以及 iOS 的黑魔法 Method Swizzling。然后,再理解页面浏览事件($AppViewScreen)全埋点的实现原理。

一、UIViewController 生命周期

1. viewDidLoad:视图加载完成时调用。

2.viewWillAppear:视图即将出现在屏幕上时调用。

3.viewDidAppear:视图已经出现在屏幕上时调用。

4.viewWillDisappear:视图即将从屏幕消失时调用。

5.viewDidDisappear:视图已经从屏幕消失时调用。

二、Method Swizzling

Method Swizzling 是一种动态修改方法调用的技巧,可以用来在运行时替换或增强方法的行为。

三、页面浏览事件全埋点实现原理

页面浏览事件全埋点是通过 Method Swizzling 技术实现的,具体步骤如下:

1. 在 UIViewController 的 viewDidLoad 方法中,使用 Method Swizzling 替换 viewDidLoad 方法,在替换后的方法中添加埋点逻辑。

2.在 viewWillAppear 方法中,使用 Method Swizzling 替换 viewWillAppear 方法,在替换后的方法中添加埋点逻辑。

3.在 viewDidAppear 方法中,使用 Method Swizzling 替换 viewDidAppear 方法,在替换后的方法中添加埋点逻辑。

4.在 viewWillDisappear 方法中,使用 Method Swizzling 替换 viewWillDisappear 方法,在替换后的方法中添加埋点逻辑。

5.在 viewDidDisappear 方法中,使用 Method Swizzling 替换 viewDidDisappear 方法,在替换后的方法中添加埋点逻辑。

通过以上步骤,我们可以实现对页面浏览事件的全面监控和埋点。

前言

​ 我们先了解 UIViewController 生命周期相关的内容和 iOS 的“黑魔法” Method Swizzling。然后再了解页面浏览事件($AppViewScreen)全埋点的实现原理

一、UIViewController 生命周期

​ 众所周知,每一个 UIViewController 都管理着一个由多个视图组成的树形结构,其中根视图保存在 UIViewController 的 view 属性中。UIViewController 会懒加载它所管理的视图集,直到第一次访问 view 属性时,才会去加载或者创建 UIViewController 的视图集。

有以下几种常用的方式加载或者创建 UIViewController 的视图集:

  • 使用 Storyboard
  • 使用 Nib 文件
  • 使用代码,即重写 - loadView

​ 以上这些方法,最终都会创建出合适的根视图并保存在 UIViewController 的 view 属性中,这是 UIViewController 生命周期的第一步。当 UIViewController 的根视图需要展示在页面上时,会调用 - viewDidLoad 方法。在这个方法中,我们可以做一些对象初始化相关的工作。

​ 需要注意的是:此时,视图的 bounds 还没有确定。对于使用代码创建视图,- viewDidLoad 方法会在 -loadView 方法调用结束之后运行;如果使用的是 Stroyboard 或者 Nib 文件创建视图,- viewDidLoad 方法则会在 - awakeFromNib 方法之后调用。

​ 当 UIViewController 的视图在屏幕上的显示状态发生变化时,UIViewController 会自动回调一些方法,确保子类能够响应到这些变化。如下图所示,它展示了 UIViewController 在不同的显示状态时会回调不同的方法。

​ 在 UIViewController 被销毁之前,还会回调 - dealloc 方法,我们一般通过重写这个方法来主动释放不能被 ARC 自动释放的资源。

​ 我们现在对 UIViewController 的整个生命周期有了一些基本了解。那么,我们如何去实现页面浏览事件( $AppViewScreen 事件)的全埋点呢?

​ 通过 UIViewController 的生命周期可知,当执行到 - viewDidAppear: 方法时,表示视图已经在屏幕上渲染完成,也即页面已经显示出来了,正等待用户进行下一步操作。因此,- viewDidAppear: 方法就是我们触发页面浏览事件的最佳时机。如果想要实现页面浏览事件的全埋点,需要使用 iOS 的“黑魔法” Method Swizzling 相关的技术。

二、Method Swizzling 黑魔法

​ Method Swizzling,顾名思义,就是交换两个方法的实现。简单的来说,就是利用 Objective-C runtime 的动态绑定特性,把一个方法的实现与另一个方法的实现进行交换。

2.1 Method Swizzling 基础

​ 在 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 字段,我们再来看看 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 的映射关系,如下图所示。

那我们如何改变 method_name 和 method_imp 的映射关系呢?在 Objective-C 的 runtime 中,提供了很多非常方便使用的函数,让我们可以很简单的就能实现 Method Swizzling,即改变 method_name 和 method_imp 的映射关系,从而达到交换方法的效果。

iOS全埋点方案中,界面预览事件如何实现?

2.2 实现 Method Swizzling 的相关函数
  1. Method class_getInstanceMethod

    // 返回目标类 aClass、方法名为 aSelector 的实例方法 // aClass :目标类 // aSelector: 方法名 OBJC_EXPORT Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

  2. BOOL class_addMethod

    // 给目标类 aClass 添加一个新的方法,同时包括方法的实现 // aClass: 目标类 // aSelector: 要添加方法的方法名 // imp: 要添加方法的方法实现 // types: 方法实现的编码类型 OBJC_EXPORT BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

  3. IMP method_getImplementation

    // 返回方法实现的指针 // 目标方法 OBJC_EXPORT IMP _Nonnull method_getImplementation(Method _Nonnull m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

  4. IMP class_replaceMethod

    // 替换目标类 aClass 的 aSelector 方法指针 // aClass: 目标类 // aSelector: 目前方法的方法名 // imp:新方法的方法实现 // types: 方法实现的编码类型 OBJC_EXPORT IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

  5. void method_exchangeImplementations

    // 交换2个方法的实现指针 // m1: 交换方法1 // m2: 交换方法2 OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

2.3 实现 Method Swizzling

第一步 创建 NSObject 的分类 NSObject+SASwizzler

第二步 在 NSObject+SASwizzler.h 声明方法交换方法

/// 交换方法名为 originalSEL 和方法名为 alternateSEL 两个方法实现 /// @param originalSEL 原始的方法名称 /// @param alternateSEL 要交换的方法名称 + (BOOL)sensorsdata_swizzleMethod:(SEL)originalSEL withMethod:(SEL)alternateSEL;

第三步 在 NSObject+SASwizzler.m 实现方法的交换

+ (BOOL)sensorsdata_swizzleMethod:(SEL)originalSEL withMethod:(SEL)alternateSEL { // 获取原始方法 Method originalMethod = class_getInstanceMethod(self, originalSEL); // 当原始的方法不存在时,返回NO,表示 Swizzler 失败 if (!originalMethod) { return NO; } // 获取要交换的方法 Method alternateMethod = class_getInstanceMethod(self, alternateSEL); // 当交换的方法不存在时,返回NO,表示 Swizzler 失败 if (!alternateMethod) { return NO; } // 交换两个方法的实现 method_exchangeImplementations(originalMethod, alternateMethod); return YES; } 三、实现界面预览事件全埋点

​ 利用方法交换,来交换 UIViewController 的 -viewDidAppear: 方法,然后在方法交换中触发 $AppViewScreen 事件,来实现界面预览的全埋点。

3.1 实现步骤

第一步:在 SensorsSDK 项目中,新增一个 UIViewController 类别 UIViewController+SensorsData

第二步:在 UIViewController+SensorsData.m 类别新增交换方法 - sensorsdata_viewDidAppear:,然后再交换方法中调用原始方法,并触发 $AppViewScreen 事件

- (void)sensorsdata_viewDidAppear:(BOOL)animated { // 调用原始方法, 即 - viewDidAppear [self sensorsdata_viewDidAppear:animated]; // 触发 $AppViewScreen 事件 NSMutableDictionary *properties = [NSMutableDictionary dictionary]; [properties setValue:NSStringFromClass([self class]) forKey:@"$screen_name"]; [properties setValue:self.navigationItem.title forKey:@"$title"]; [[SensorsAnalyticsSDK sharedInstance] track:@"$AppViewScreen" properties:properties]; }

第三步: 在 UIViewController+SensorsData.m 中重写 + load 类方法,并在 + load 类方法中调用 NSObject+SASwizzler 的类方法交换

+ (void)load { [UIViewController sensorsdata_swizzleMethod:@selector(viewDidAppear:) withMethod:@selector(sensorsdata_viewDidAppear:)]; }

第四步 : 测试验证

{ "event" : "$AppViewScreen", "time" : 1648626597682, "propeerties" : { "$model" : "x86_64", "$manufacturer" : "Apple", "$lib_version" : "1.0.0", "$os" : "iOS", "$app_version" : "1.0", "$screen_name" : "ViewController", "$os_version" : "15.2", "$lib" : "iOS" } } 3.2 优化

问题:在应用程序启动过程中,会触发多余的 $AppViewScreen ,我们可以引入黑名单的机制,即在黑名单里配置那些 UIViewController 及子类不触发 $AppViewScreen 事件。

第一步 创建一个 sensorsdata_black_list.plist 文件,并把 root 类型改成 Array,该文件就是黑名单文件,然后在黑名单文件中添加控制器,如图所示:

第二步 在 UIViewController+SensorsData.m 文件中新增 - shouldTrackAppViewScreen 方法,用来判断当前控制器是否在黑名单中。

static NSString * const kSensorsDataBlackListFileName = @"sensorsdata_black_list"; // 黑名单 - (BOOL)shouldTrackAppViewScreen { static NSSet *blackList = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *path = [[NSBundle bundleForClass:SensorsAnalyticsSDK.class] pathForResource:kSensorsDataBlackListFileName ofType:@"plist"]; NSArray *classNames = [NSArray arrayWithContentsOfFile:path]; NSMutableSet *set = [NSMutableSet setWithCapacity:classNames.count]; for (NSString *className in classNames) { [set addObject:NSClassFromString(className)]; } blackList = [set copy]; }); for (Class cla in blackList) { if ([self isKindOfClass:cla]) { return NO; } } return YES; }

第三步 在触发 $AppViewScreen 事件之前,判断是否在黑名单中

- (void)sensorsdata_viewDidAppear:(BOOL)animated { // 调用原始方法, 即 - viewDidAppear [self sensorsdata_viewDidAppear:animated]; // 触发 $AppViewScreen 事件 if ([self shouldTrackAppViewScreen]) { NSMutableDictionary *properties = [NSMutableDictionary dictionary]; [properties setValue:NSStringFromClass([self class]) forKey:@"$screen_name"]; [properties setValue:self.navigationItem.title forKey:@"$title"]; [[SensorsAnalyticsSDK sharedInstance] track:@"$AppViewScreen" properties:properties]; } }

第四步 测试验证

​ 运行Demo,所添加到黑名单中的 controller 不会发送 $AppViewScreen 事件。

3.4 遗留问题

​ 按照目前的方案实现 $AppViewScreen 事件的全埋点,会有2个问题:

应用程序热启动是(从后台恢复),第一个界面没有触发 $AppViewScreen 事件。原因是这个界面没有再次执行 - viewDidAppear: 方法

要求 UIViewController 的子类不重写 -viewDidAppear:方法,一旦重写,必须调用[super viewDidAppear:animated], 否则不会触发 $AppViewScreen 事件。原因是直接交换了 UIViewController 的 - viewDidAppear: 方法。