iOS数据埋点统计方案(附Demo): 运行时Method Sw
1. 场景需求
统计UIViewController加载次数
统计UIButton点击次数
统计自定义方法的执行
统计UITableView的Cell点击事件
工程说明,首页Test1ViewController,其中有4个按钮,点击第一个按钮打印,第二个到第四个按钮分别跳转到Test2ViewController,Test3ViewController,Test4ViewController。
技术选型:
手动复制统计的代码逻辑一个个地粘贴到需要统计的类和方法中去。工作量大,可维护性差,仅适用统计埋点极少的情况。
通过继承和重写系统方法 -- 利用写好统计的一个基类,让需要VC继承自该基类,或者调用重写过统计逻辑的按钮基类等等。
简单的分类,添加类方法或者示例方法 -- 将统计逻辑封装在分类方法里面,在需要统计的地方导入并调用分类方法。
替换系统方法的分类:通过运行时Runtime的办法 -- 利用Method Swizzling机制进行方法替换:替换原来的需要在里面统计却不含统计逻辑的方法 为 新的包含了统计逻辑的方法。
通过AOP的方法 -- 利用Aspect框架对需要进行统计的方法进行挂钩(hook),并注入包含了统计逻辑的代码块(block)。
其实做为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS学习交流群 651612063,不管你是小白还是大牛欢迎入驻,大家一起交流学习,
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 =0,/// 在原始方法后调用(默认)
AspectPositionInstead =1,/// 替换原始方法
AspectPositionBefore =2,/// 在原始方法前调用
AspectOptionAutomaticRemoval =1<<3/// 在执行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,0), ^{
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,0), ^{
block(aspectInfo);
});
}error:NULL];
}
}
}
}
@end
5. 在AppDelegate的类方法中根据Plist监听清单进行HOOK
场景需求:需要监听不同类,不同按钮,系统方法,及表单元点击事件
方案特点:是可代码配置需要监听的清单Plist,但是不能将需要注入的统计代码块block写在这个清单Plist里面。
EventList.plist
Appdelegate.m调用
[AspectMananer trackBttonEvent];
其实做为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS学习交流群 651612063,不管你是小白还是大牛欢迎入驻,大家一起交流学习,加群私聊(小狄)就可以领取2018最全梳理的面试宝典和资料)。同时想要找工作的也可以私聊小编。
需要资料的也可以加小编QQ2507362121。