在需要统计用户行为的时候,传统点做法就是在需要统计的地方加入响应的代码,如viewWillAppear、viewWillDisappear和一些交互事件中。优雅一点可以自定义一个控制器让其它控制器继承。代码耦合,扩展性差,不易维护,这些都是上述的方法的一些弊端。昨天说的Runtime黑魔法可以很优雅方便地解决这些弊端。
具体实现
给UIViewController写个分类, + (void)load 这个类方法是在一个类被读到内存后,Runtime就会给它发送load消息,所以这个方法调用得很早,适合用来埋点。
下面混合每个UIViewController的viewDidAppear,也就是在执行原来的viewDidAppear之前先执行统计方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #import "UIViewController+swizzling.h" #import <objc/runtime.h> @implementation UIViewController (swizzling) + (void)load { SEL origSel = @selector(viewDidAppear:); SEL swizSel = @selector(swiz_viewDidAppear:); [UIViewController swizzleMethods:[self class] originalSelector:origSel swizzledSelector:swizSel]; }
+ (void)swizzleMethods:(Class)class originalSelector:(SEL)origSel swizzledSelector:(SEL)swizSel { Method origMethod = class_getInstanceMethod(class, origSel); Method swizMethod = class_getInstanceMethod(class, swizSel); BOOL didAddMethod = class_addMethod(class, origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod)); if (didAddMethod) { class_replaceMethod(class, swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); } else { method_exchangeImplementations(origMethod, swizMethod); } } - (void)swiz_viewDidAppear:(BOOL)animated { NSLog(@"I am in - [swiz_viewDidAppear:]"); [self swiz_viewDidAppear:animated]; } @end
|
如果想统计工程中UIButton的点击事件可以从UIControl的这个方法入手
1
| - (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event;
|
第三方库的封装
参考使用Github上的这个库Aspect
Aspect库是对面向切面编程(Aspect Oriented Programming)的实现,里面封装了Runtime的方法,也封装了上文的Method Swizzling方法。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| + (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error { return aspect_add((id)self, selector, options, block, error); }
- (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error { return aspect_add(self, selector, options, block, error); }
|
参考一篇博客的做法,可以把所有埋点操作放到一个类方法中,在程序启动时调用即可
1 2 3 4 5 6 7 8 9 10 11 12
| + (void)createAllHooks { [UIViewController aspect_hookSelector:@selector(viewDidLoad) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info){ NSLog(@"[ASPECT] inject in class instance:%@", [info instance]); } error:NULL]; }
|