微信号:iOSDevTip

介绍:最新iOS、iPhone资讯,万名iOS开发者、swift开发、果粉聚集,参与技术讨论,整理开发技巧,分享创业经验!享受生活、热爱编程!

iOS数据埋点统计方案(附Demo): 运行时Method Swizzling机制与AOP编程(面向切面编程

2018-06-13 19:01 iOS开发

点击上方“iOS开发”,选择“置顶公众号”

关键时刻,第一时间送达!

 

1. 场景需求


  • 统计UIViewController加载次数

  • 统计UIButton点击次数

  • 统计自定义方法的执行

  • 统计UITableView的Cell点击事件


工程说明,首页Test1ViewController,其中有4个按钮,点击第一个按钮打印,第二个到第四个按钮分别跳转到Test2ViewControllerTest3ViewControllerTest4ViewController

 

技术选型:

 

  • 手动复制统计的代码逻辑一个个地粘贴到需要统计的类和方法中去。工作量大,可维护性差,仅适用统计埋点极少的情况。

  • 通过继承和重写系统方法 -- 利用写好统计的一个基类,让需要VC继承自该基类,或者调用重写过统计逻辑的按钮基类等等。

  • 简单的分类,添加类方法或者示例方法 -- 将统计逻辑封装在分类方法里面,在需要统计的地方导入并调用分类方法。

  • 替换系统方法的分类:通过运行时Runtime的办法 -- 利用Method Swizzling机制进行方法替换:替换原来的需要在里面统计却不含统计逻辑的方法 为 新的包含了统计逻辑的方法。

  • 通过AOP的方法 -- 利用Aspect框架对需要进行统计的方法进行挂钩(hook),并注入包含了统计逻辑的代码块(block)。


2. 为VC设计的分类:运行时Method Swizzling方案



场景需求:需要监听全局的某一类的同一方法 


这种方案被监听的方法单一,但会影响全局的所有的类的该方法。例如下面的分类,即使你不import,只要存在于工程就会影响。

 

  • UIViewController+Trace

 

#import "UIViewController+Trace.h"
#import "TraceHandler.h"
#import <objc/runtime.h>
#import <objc/objc.h>
#import "Aspects.h"
@implementation UIViewController (Trace)
#pragma mark - 1.自定义实现方法
+ (void)load{
   swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}
- (void)swizzled_viewDidAppear:(BOOL)animated{
   // call original implementation
   [self swizzled_viewDidAppear:animated];
   // Begin statistics Event
   [TraceHandler statisticsWithEventName:@"UIViewController"];
}
void swizzleMethod(Class class,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
   BOOL didAddMethod = 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"
@implementation TraceHandler
+ (void)statisticsWithEventName:(NSString *)eventName{
   NSLog(@"-----> %@",eventName);
}
@end


3. 为VC设计的分类:AOP编程方案


场景需求:该方案的适用特点同上第二节。

 

Aspects 是iOS平台一个轻量级的面向切面编程(AOP)框架,只包括两个方法:一个类方法,一个实例方法。


+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                     withOptions:(AspectOptions)options
                      usingBlock:(id)block
                           error:(NSError **)error;


- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                     withOptions:(AspectOptions)options
                      usingBlock:(id)block
                           error:(NSError **)error;


函数使用方式简单易懂,挂钩的方式为三种:


typedef NS_OPTIONS(NSUInteger, AspectOptions) {
   AspectPositionAfter   = 0,            /// 在原始方法后调用(默认)
   AspectPositionInstead = 1,            /// 替换原始方法
   AspectPositionBefore  = 2,            /// 在原始方法前调用
   AspectOptionAutomaticRemoval = 1 << 3 /// 在执行1次后自动移除
};


调用示例代码:


[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
   NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];


这段代码是给UIViewController的viewWillAppear:挂钩一个Block,在原始方法执行完成后,打印字符串。

 

  • UIViewController+Trace


#pragma mark - 2.使用Aspects框架
+ (void)load{
   [UIViewController aspect_hookSelector:@selector(viewDidAppear:)
                             withOptions:AspectPositionAfter
                              usingBlock:^(id<AspectInfo>aspectInfo){
                                  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"
@implementation AppDelegate (Trace)
+ (void)setupLogging{
   NSDictionary *configDic = @{
                               @"ViewController":@{
                                       @"des":@"show ViewController",
                                       },
                               @"Test1ViewController":@{
                                       @"des":@"show Test1ViewController",
                                       @"TrackEvents":@[@{
                                                            @"EventDes":@"click action1",
                                                            @"EventSelectorName":@"action1",
                                                            @"block":^(id<AspectInfo>aspectInfo){
                                                                NSLog(@"统计 Test1ViewController action1 点击事件");
                                                            },
                                                            },
                                                        @{
                                                            @"EventDes":@"click action2",
                                                            @"EventSelectorName":@"action2",
                                                            @"block":^(id<AspectInfo>aspectInfo){
                                                                NSLog(@"统计 Test1ViewController action2 点击事件");
                                                            },
                                                            }],
                                       },
                               @"Test2ViewController":@{
                                       @"des":@"show Test2ViewController",
                                       }
                               };
   [TraceManager setUpWithConfig:configDic];
}
@end


  • TraceManager.m


#import "TraceManager.h"
@import UIKit;
typedef void (^AspectHandlerBlock)(id<AspectInfo> aspectInfo);
@implementation TraceManager
+ (void)setUpWithConfig:(NSDictionary *)configDic{
   // hook 所有页面的viewDidAppear事件
   [UIViewController aspect_hookSelector:@selector(viewDidAppear:)
                             withOptions:AspectPositionAfter
                              usingBlock:^(id<AspectInfo> 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 *className in configDic) {
       Class clazz = NSClassFromString(className);
       NSDictionary *config = configDic[className];
       if (config[@"TrackEvents"]) {
           for (NSDictionary *event in config[@"TrackEvents"]) {
               SEL selekor = NSSelectorFromString(event[@"EventSelectorName"]);
               AspectHandlerBlock block = event[@"block"];
               [clazz aspect_hookSelector:selekor
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<AspectInfo> 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];


  • 原文链接:https://www.jianshu.com/p/3ac8130b72eb

  • iOS开发整理发布,转载请联系作者获得授权

【点击成为源码大神】

 
iOS开发 更多文章 iOS皮肤切换方案 编写可读代码的艺术 C、C++、Java、JavaScript、PHP、Python分别用来开发什么? Method Swizzling在项目中的实际运用 为何iPhone X的“刘海”设计看起来优于其他Android仿冒者
猜您喜欢 使用Clipboard History 2高效的管理剪贴板 | 利器 小道消息预告 (12.6 晚上 10:00)—— 测试工程师需要哪些技能? 程序员的困境 Hadoop启动报Error: JAVA_HOME is not set and could not be found解决办法 Android 开发中 Kotlin 会取代 Java 吗?