36选7玩法 >iOS开发

懒人做开发系列:利用Object-C特性埋点

2018-04-24 14:18 编辑: suiling 分类:iOS开发 来源:moonCoder

原文

Objective-C是一门简单的语言,95%是C。只是在语言层面上加了些关键字和语法。真正让Objective-C如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。

运行时会发消息给对象。一个对象的class保存了方法列表。那么这些消息是如何映射到方法的,这些方法又是如何被执行的呢?第一个问题的答案很简单。class的方法列表其实是一个字典,key为selectors,IMPs为value。一个IMP是指向方法在内存中的实现。很重要的一点是,selector和IMP之间的关系是在运行时才决定的,而不是编译时。这样我们就能玩出些花样。

这次我们就是利用运行时来进行配置化的埋点。首先说下什么是埋点:所谓埋点就是在应用中特定的流程收集一些信息,用来跟踪应用使用的状况,后续用来进一步优化产品或是提供运营的数据支撑,包括访问(Visits),访客(Visitor),停留时间(Time On Site),页面查看(Page Views,又称为页面浏览)和跳出率(Bounce Rate,又可称为蹦失率)。这样的信息收集可以大致分为两种:页面统计(track this virtual page view),统计操作行为(track this button by an event)。

这种的正常做法就是在各自的页面的viewWillAppear以及按钮的点击实现里去加代码传输数据给服务端进行统计,这种方式虽然省脑子,但是既耗时间,也不便于后期维护。

利用语言的特性我们对这种方式进行改进,首先我们要用到Aspects框架,Aspects是iOS平台一个轻量级的面向切面编程(AOP)框架,只包括两个方法:一个类方法,一个实例方法。核心原理就是:

下面我们来看下实现:首先需要新建一个plist把你需要的埋点都加进去:

然后看下代码实现:

- (void)trackEvent {
   // Hook viewcontroller
   NSString *filePath = [[NSBundle mainBundle] pathForResource:@"KZWList" ofType:@"plist"];
   NSDictionary *configs = [NSDictionary dictionaryWithContentsOfFile:filePath];
   
   [UIViewController aspect_hookSelector:@selector(viewWillAppear:)
                             withOptions:AspectPositionAfter
                              usingBlock:^(id aspectInfo) {
                                  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                                      NSString *className = NSStringFromClass([[aspectInfo instance] class]);
                                      NSString *pageImp = configs[className][@"KZWTrackPageName"];
                                      if (pageImp) {
                                          id tracker = [[GAI sharedInstance] defaultTracker];
                                          [tracker set:kGAIScreenName value:pageImp];
                                          [tracker send:[[GAIDictionaryBuilder createScreenView] build]];
                                      }
                                  });
                              } error:NULL];
                              
   // Hook Events
   for (NSString *className in configs) {
       Class clazz = NSClassFromString(className);
       NSDictionary *config = configs[className];
       NSString *pageImp = configs[className][@"KZWTrackPageName"];
       if (config[@"KZWTrackEvents"]) {
           for (NSDictionary *event in config[@"KZWTrackEvents"]) {
               SEL selekor = NSSelectorFromString(event[@"KZWEventSelector"]);
               [clazz aspect_hookSelector:selekor
                              withOptions:AspectPositionAfter
                               usingBlock:^(id aspectInfo) {
                                   //将参数发到自己服务器
                                   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                                   id tracker = [[GAI sharedInstance] defaultTracker];
                                   [tracker send:[[GAIDictionaryBuilder createEventWithCategory:pageImp
                                                                                         action:event[@"KZWEventAction"]
                                                                                          label:event[@"KZWEventName"]
                                                                                          value:nil] build]];
                                       });
                               } error:NULL];
                               
           }
       }
   }
}

下面我们来说说该方案的缺陷:

1、并不是所有的事件都是有继承自UIControl的控件来发出的,比如:手势,点击Cell。

2、并不是所有的按钮点击了之后就立马需要埋点上传?可能在按钮的响应方法中经过了层层的if(){ } else{ }最后才需要埋点。

3、如果有参数

4、对于代理方法该怎样处理?

5、如果很多个按钮对应着一个事件该怎样处理?

6、项目中事件的处理方法不尽相同,方法的参数个数不一样,并且方法的返回值也不一样,如何对他们进行统一的处理?

下面我们来一一解决这些问题。

问题1:对于不是来自UIControl的子类发出的事件,我们一样是可以进行hooK,只不过方法有所不同。我们在UIControl的分类中写了一段嵌入的代码,确实hook住了系统UIButton的点击事件,是因为UIButton自身会调用UIControl的这个方法。但是对于点击事件,这个是我们自己写的一个方法,它的父类UIViewController中是没有的,所以在执行我们自己点击事件的方法时UIViewController分类中要嵌入的方法是不会被调用的,这时候怎么办,我们可以动态的给我们自己要hook的ViewController动态的添加一个方法,然后就可以hook了(这一点不太好理解)。具体的添加方法,可以参考本文的实例代码。

问题2:对于是否上传和具体的业务逻辑相关的情况,我们可以用方法所在类的一个属性值进行标记,这个属性写在.m文件中即可(KVC可以获取.m文件中的属性值。),我们先执行要hook那个类的方法,然后根据plist中配置的相关标记进行相应的处理(这里的属性值其实也是不必要的,我么可以根据类名和方法名字符串的哈希生成唯一的key,然后利用runtime自动关联到这个类的mf_condition属性上,这个属性是一个字典其key就是刚才生成的,value就是运行完这个方法之后得到的值,然后这个值再跟plist中的配置做以比较)。

问题3:对于和事件所在类有紧密关联的埋点数据,比如某个页面对应的产品ID,比如某个页面点击了cell,之后这个cell对应的model的ID。这个时候我们可以参考方法2,添加一个属性,用一个属性值来存储这些这些需要上传的具体数据。

问题4:代理方法和手势的处理也是一样的,既然一个类实现了某个代理方法,那么其[someInstance respondsToSelector:someSelector]所返回的BOOL值应该是YES的,然后其它的就和手势的处理是一样的了。

问题5:对于很多按钮对应一个响应事件的情况,我们可以利用RunTime动态的给按钮添加一个属性,比如:buttonIdentifier,这样我们就可以在plist中进行相应的配置,以进行相应的埋点处理。

问题6:这个问题其实就是hook住所有的方法,然后给他们添加同一个代码段的问题,这时候我们可以使用Aspects这个第三方框架:

+ (id)aspect_hookSelector:(SEL)selector
                  withOptions:(AspectOptions)options
                   usingBlock:(id)block
                        error:(NSError **)error {
return aspect_add((id)self, selector, options, block, error);
 }

调用这个接口,因为在UIViewController的分类中调用这个接口的对象不一样,并且我们根据plist中的配置hook的selector不一样,然而最后执行的block却是一样的,这就很好的解决了问题。

实在不好这样埋的部分埋点,可以选择方法一进行埋点。

作者:moonCoder

链接:https://www.jianshu.com/p/0b38d4041886

搜索CocoaChina微信公众号:CocoaChina
微信扫一扫
订阅每日移动开发及APP推广热点资讯
公众号:
CocoaChina
我要投稿   收藏文章
上一篇:iOS中Block的用法,举例,解析与底层原理(这可能是最详细的Block解析)
下一篇:iOS进阶补完计划--通读runtime

相关资讯

我来说两句
发表评论
您还没有登录!请登录注册
所有评论(0

综合评论

相关帖子

sina weixin mail 回到顶部
幸运飞艇5码公式 | 幸运飞艇开奖直播app | 北京pk | 幸运农场官网 | 重庆幸运农场预测结果 | 重庆幸运农场开奖历史 | 北京赛车pk10高手心得 | 幸运飞艇定位公式 | 871| 309| 30| 379| 803| 705| 465| 854| 659| 31|