iOS数据埋点统计方案:运行时Method Swizzling机制与AOP编程

1. 场景需求

统计UIViewController加载次数

统计UIButton点击次数

统计自定义方法的执行

统计UITableView的Cell点击事件

工程说明,首页Test1ViewController,其中有4个按钮,点击第一个按钮打印,第二个到第四个按钮分别跳转到Test2ViewController,Test3ViewController,Test4ViewController。

技术选型:

手动复制统计的代码逻辑一个个地粘贴到需要统计的类和方法中去。工作量大,可维护性差,仅适用统计埋点极少的情况。

通过继承和重写系统方法 -- 利用写好统计的一个基类,让需要VC继承自该基类,或者调用重写过统计逻辑的按钮基类等等。

简单的分类,添加类方法或者示例方法 -- 将统计逻辑封装在分类方法里面,在需要统计的地方导入并调用分类方法。

替换系统方法的分类:通过运行时Runtime的办法--利用Method Swizzling机制进行方法替换:替换原来的需要在里面统计却不含统计逻辑的方法 为 新的包含了统计逻辑的方法。

通过AOP的方法--利用Aspect框架对需要进行统计的方法进行挂钩(hook),并注入包含了统计逻辑的代码块(block)。

2. 为VC设计的分类:运行时Method Swizzling方案

场景需求:需要监听全局的某一类的同一方法

这种方案被监听的方法单一,但会影响全局的所有的类的该方法。例如下面的分类,即使你不import,只要存在于工程就会影响。

UIViewController+Trace

#import"UIViewController+Trace.h"

#import"TraceHandler.h"

#import

#import

#import"Aspects.h"

@implementationUIViewController(Trace)

#pragma mark - 1.自定义实现方法

+ (void)load{

swizzleMethod([selfclass],@selector(viewDidAppear:),@selector(swizzled_viewDidAppear:));

}

- (void)swizzled_viewDidAppear:(BOOL)animated{

// call original implementation

[selfswizzled_viewDidAppear:animated];

// Begin statistics Event

[TraceHandler statisticsWithEventName:@"UIViewController"];

}

voidswizzleMethod(Classclass,SEL originalSelector,SEL swizzledSelector){

// the method might not exist in the class, but in its superclass

Method originalMethod = class_getInstanceMethod(class, originalSelector);

Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

// class_addMethod will fail if original method already exists

BOOLdidAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

// the method doesn’t exist and we just added one

if(didAddMethod) {

class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));

}

else{

method_exchangeImplementations(originalMethod, swizzledMethod);

}

}

@end

TraceHandler.m

#import"TraceHandler.h"

@implementationTraceHandler

+ (void)statisticsWithEventName:(NSString*)eventName{

NSLog(@"-----> %@",eventName);

}

@end

3. 为VC设计的分类:AOP编程方案

场景需求:该方案的适用特点同上第二节。

Aspects 是iOS平台一个轻量级的面向切面编程(AOP)框架,只包括两个方法:一个类方法,一个实例方法。

+ (id)aspect_hookSelector:(SEL)selector

withOptions:(AspectOptions)options

usingBlock:(id)block

error:(NSError**)error;

-(id)aspect_hookSelector:(SEL)selector

withOptions:(AspectOptions)options

usingBlock:(id)block

error:(NSError**)error;

函数使用方式简单易懂,挂钩的方式为三种:

typedefNS_OPTIONS(NSUInteger, AspectOptions) {

AspectPositionAfter =,/// 在原始方法后调用(默认)

AspectPositionInstead =1,/// 替换原始方法

AspectPositionBefore =2,/// 在原始方法前调用

AspectOptionAutomaticRemoval =1

};

调用示例代码:

[UIViewControlleraspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id aspectInfo,BOOLanimated) {

NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);

} error:NULL];

这段代码是给UIViewController的viewWillAppear:挂钩一个Block,在原始方法执行完成后,打印字符串。

UIViewController+Trace

#pragma mark - 2.使用Aspects框架

+ (void)load{

[UIViewControlleraspect_hookSelector:@selector(viewDidAppear:)

withOptions:AspectPositionAfter

usingBlock:^(idaspectInfo){

NSString*className =NSStringFromClass([[aspectInfo instance]class]);;

[TraceHandler statisticsWithEventName:className];

} error:nil];

}

4. 为全局AppDelegate设计的分类:AOP编程方案

场景需求:需要监听不同类,不同按钮,系统方法,及表单元点击事件

方案特点:是可代码配置需要监听的清单字典,并且需要注入的统计代码块block也可以写在这个清单里面。

AppDelegate+Trace.m

#import"AppDelegate+Trace.h"

#import"TraceManager.h"

@implementationAppDelegate(Trace)

+ (void)setupLogging{

NSDictionary*configDic = @{

@"ViewController":@{

@"des":@"show ViewController",

},

@"Test1ViewController":@{

@"des":@"show Test1ViewController",

@"TrackEvents":@[@{

@"EventDes":@"click action1",

@"EventSelectorName":@"action1",

@"block":^(idaspectInfo){

NSLog(@"统计 Test1ViewController action1 点击事件");

},

},

@{

@"EventDes":@"click action2",

@"EventSelectorName":@"action2",

@"block":^(idaspectInfo){

NSLog(@"统计 Test1ViewController action2 点击事件");

},

}],

},

@"Test2ViewController":@{

@"des":@"show Test2ViewController",

}

};

[TraceManager setUpWithConfig:configDic];

}

@end

TraceManager.m

#import"TraceManager.h"

@importUIKit;

typedefvoid(^AspectHandlerBlock)(id aspectInfo);

@implementationTraceManager

+ (void)setUpWithConfig:(NSDictionary*)configDic{

// hook 所有页面的viewDidAppear事件

[UIViewControlleraspect_hookSelector:@selector(viewDidAppear:)

withOptions:AspectPositionAfter

usingBlock:^(id aspectInfo){

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,), ^{

NSString*className =NSStringFromClass([[aspectInfo instance]class]);

NSString*des = configDic[className][@"des"];

if(des) {

NSLog(@"%@",des);

}

});

} error:NULL];

for(NSString*classNameinconfigDic) {

Class clazz =NSClassFromString(className);

NSDictionary*config = configDic[className];

if(config[@"TrackEvents"]) {

for(NSDictionary*eventinconfig[@"TrackEvents"]) {

SEL selekor =NSSelectorFromString(event[@"EventSelectorName"]);

AspectHandlerBlock block = event[@"block"];

[clazz aspect_hookSelector:selekor

withOptions:AspectPositionAfter

usingBlock:^(id aspectInfo){

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,), ^{

block(aspectInfo);

});

}error:NULL];

}

}

}

}

@end

5. 在AppDelegate的类方法中根据Plist监听清单进行HOOK

场景需求:需要监听不同类,不同按钮,系统方法,及表单元点击事件

方案特点:是可代码配置需要监听的清单Plist,但是不能将需要注入的统计代码块block写在这个清单Plist里面。

EventList.plist

Appdelegate.m调用

[AspectMananer trackBttonEvent];

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180529B0BLZI00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券