用户行为统计(User Behavior Statistics, UBS)一直是移动互联网产品中必不可少的环节,也俗称埋点。在保证移动端流量不会受较大影响的前提下,PM们总是希望埋点覆盖面越广越好。目前常规的做法是将埋点代码封装成工具类,但凡工程中需要埋点(如点击事件、页面跳转)的地方都插入埋点代码。一旦项目越来越复杂,你会发现埋点的代码散落在程序的各个角落,不利于维护以及复用。本文旨在探讨利用iOS的运行时机制实现一种可复、解耦、容易维护的用户统计方案。探讨毕竟是探讨,欢迎到在简书留言讨论。本文虽有些长却是用心之作,希望你有耐心看完。
注:本文需要一些iOS的Runtime基础
该方案的完成将会用到以下知识:
- Method Swizzling(Hook)
- 单元测试
一、常规埋点做法
接着开头的话题,我们先回顾一下主流的埋点是怎么做的。我粗糙地将埋点分为两种:1、页面统计,包括页面停留时间、页面进入次数;2、交互事件统计,包括单击、双击、手势交互等。
1)常规页面统计埋点
以统计页面进入次数为例,最简单粗暴的做法是在所有页面的viewDidAppear:
以及viewDidDisappear:
中分别埋点,将自己对应的pageID上传给服务端。代码大概长酱紫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@implementation HomeViewController //...other methods - (void)viewDidAppear:(BOOL)animated { [super viewWillAppear:animated]; [WUserStatistics sendEventToServer:@"PAGE_EVENT_HOME_ENTER"]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [WUserStatistics sendEventToServer:@"PAGE_EVENT_HOME_LEAVE"]; } @end |
+[WUserStatistics sendEventToServer:]
封装网络请求,将ID上传给服务器。上述方案有以下弊端:
1、复用性差。这部分埋点代码很难给其他项目复用
2、工作量大。尤其当页面较多时,需要修改的代码较多
3、引入“脏代码”,不易维护
第3点提到的“脏代码”意思是用户行为分析这种业务其实跟主业务没太大关系,不应该保持如此高的耦合度
,因为这些代码会干扰我们对项目主业务的维护。这个我个人看法。
2)常规交互事件埋点
常规做法一般在交互事件的selector中获取该事件的ID并上传给服务端,代码大概长酱紫:
1 2 3 4 5 |
- (IBAction)onFavBtnPressed:(id)sender { [WUserStatistics sendEventToServer:@"CTRL_EVENT_HOME_FAV"]; //...do other things } |
稍微大一点的APP如果采用这种方式,那诸如此类的埋点代码将遍地都是。它的缺点参考页面统计埋点部分,其复用性基本为零,也就是在新项目中根本无法复用埋点代码。
小总结一下,采用常规的做法虽然直观方便,但在可复用性、可维护性等方面有所欠缺。在我看来,借助运行时可以很好地避开这些缺点。
二、Method Swizzling、Hook与代码注入
由于Runtime知识不属于本文的重点,这里只简单介绍。
在iOS中,我们可以在运行时替换两个方法的实现,达到“勾住”某个方法并注入代码的目的。具体做法是:
重载类的“+(void)load”方法,在程序加载到内存时利用Runtime的
method_exchangeImplementations
等接口将方法(设为M)的实现互相交换。当方法M被调用时就会被勾住(Hook),执行我们的方法。
这种技术也称为Method Swizzling
,属于面向切面编程(Aspect-Oriented Programming)的一种实现。
替换两个方法的实现,代码一般长酱紫:
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 |
@interface WHookUtility : NSObject + (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector; @end @implementation WHookUtility + (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector { Class class = cls; Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } @end |
这个WHookUtility
工具类下文会用到。比如现在我们要勾住UIViewController
的viewWillAppear:
方法,可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@implementation UIViewController (userStastistics) + (void)load { static dispatch_once_t ons="crayon-sy">(void)load { static dispatch_once_t on Ȑ工具类,但凡工程中需要埋点(如点击事件、页面跳转)的地方都插入埋点代码。一旦项目越来越复杂,你会发现埋点的代码散落在程序的各个角落,不利于维护以及复用。本文旨在探讨利用iOS的运行时机制实现一种可复、解耦、容易维护的用户统计方案。探讨毕竟是探讨,欢迎到在简书留言讨论。本文虽有些长却是用心之作,希望你有耐心看完。
该方案的完成将会用到以下知识:
一、常规埋点做法接着开头的话题,我们先回顾一下主流的埋点是怎么做的。我粗糙地将埋点分为两种:1、页面统计,包括页面停留时间、页面进入次数;2、交互事件统计,包括单击、双击、手势交互等。 1)常规页面统计埋点以统计页面进入次数为例,最简单粗暴的做法是在所有页面的
第3点提到的“脏代码”意思是用户行为分析这种业务其实跟主业务没太大关系, 2)常规交互事件埋点常规做法一般在交互事件的selector中获取该事件的ID并上传给服务端,代码大概长酱紫:
稍微大一点的APP如果采用这种方式,那诸如此类的埋点代码将遍地都是。它的缺点参考页面统计埋点部分,其复用性基本为零,也就是在新项目中根本无法复用埋点代码。 小总结一下,采用常规的做法虽然直观方便,但在可复用性、可维护性等方面有所欠缺。在我看来,借助运行时可以很好地避开这些缺点。 二、Method Swizzling、Hook与代码注入由于Runtime知识不属于本文的重点,这里只简单介绍。
这种技术也称为 替换两个方法的实现,代码一般长酱紫:
这个
|