<i id='dblap'><div id='dblap'><ins id='dblap'></ins></div></i>

  1. <acronym id='dblap'><em id='dblap'></em><td id='dblap'><div id='dblap'></div></td></acronym><address id='dblap'><big id='dblap'><big id='dblap'></big><legend id='dblap'></legend></big></address>
    1. <i id='dblap'></i>
        <fieldset id='dblap'></fieldset>
        <dl id='dblap'></dl>

        <ins id='dblap'></ins>

        <code id='dblap'><strong id='dblap'></strong></code>

        <span id='dblap'></span>
      1. <tr id='dblap'><strong id='dblap'></strong><small id='dblap'></small><button id='dblap'></button><li id='dblap'><noscript id='dblap'><big id='dblap'></big><dt id='dblap'></dt></noscript></li></tr><ol id='dblap'><table id='dblap'><blockquote id='dblap'><tbody id='dblap'></tbody></blockquote></table></ol><u id='dblap'></u><kbd id='dblap'><kbd id='dblap'></kbd></kbd>

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

          • 时间:
          • 浏览:8
          • 来源:124软件资讯网

            原文

            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却是一样的  ,这就很好的解决了问题 。

            着实欠好这样埋的部门埋点 ,可以选择要领一举行埋点  。