美文网首页程序员
iOS数据埋点统计方案(附Demo): 运行时Method Sw

iOS数据埋点统计方案(附Demo): 运行时Method Sw

作者: 小狄_57b5 | 来源:发表于2018-06-15 23:50 被阅读14次

    1. 场景需求

    统计UIViewController加载次数

    统计UIButton点击次数

    统计自定义方法的执行

    统计UITableView的Cell点击事件

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

    技术选型:

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

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

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

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

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

    其实做为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS学习交流群 651612063,不管你是小白还是大牛欢迎入驻,大家一起交流学习,

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

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

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

    UIViewController+Trace

    #import"UIViewController+Trace.h"

    #import"TraceHandler.h"

    #import

    #import

    #import"Aspects.h"

    @implementationUIViewController(Trace)

    #pragma mark - 1.自定义实现方法

    + (void)load{

    swizzleMethod([selfclass],@selector(viewDidAppear:),@selector(swizzled_viewDidAppear:));

    }

    - (void)swizzled_viewDidAppear:(BOOL)animated{

    // call original implementation

    [selfswizzled_viewDidAppear:animated];

    // Begin statistics Event

    [TraceHandler statisticsWithEventName:@"UIViewController"];

    }

    voidswizzleMethod(Classclass,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

    BOOLdidAddMethod = 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"

    @implementationTraceHandler

    + (void)statisticsWithEventName:(NSString*)eventName{

    NSLog(@"-----> %@",eventName);

    }

    @end

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

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

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

    + (id)aspect_hookSelector:(SEL)selector

    withOptions:(AspectOptions)options

    usingBlock:(id)block

    error:(NSError**)error;

    -(id)aspect_hookSelector:(SEL)selector

    withOptions:(AspectOptions)options

    usingBlock:(id)block

    error:(NSError**)error;

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

    typedefNS_OPTIONS(NSUInteger, AspectOptions) {

    AspectPositionAfter   =0,/// 在原始方法后调用(默认)

    AspectPositionInstead =1,/// 替换原始方法

    AspectPositionBefore  =2,/// 在原始方法前调用

    AspectOptionAutomaticRemoval =1<<3/// 在执行1次后自动移除

    };

    调用示例代码:

    [UIViewControlleraspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id aspectInfo,BOOLanimated) {

    NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);

    } error:NULL];

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

    UIViewController+Trace

    #pragma mark - 2.使用Aspects框架

    + (void)load{

    [UIViewControlleraspect_hookSelector:@selector(viewDidAppear:)

    withOptions:AspectPositionAfter

    usingBlock:^(idaspectInfo){

    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"

    @implementationAppDelegate(Trace)

    + (void)setupLogging{

    NSDictionary*configDic = @{

    @"ViewController":@{

    @"des":@"show ViewController",

    },

    @"Test1ViewController":@{

    @"des":@"show Test1ViewController",

    @"TrackEvents":@[@{

    @"EventDes":@"click action1",

    @"EventSelectorName":@"action1",

    @"block":^(idaspectInfo){

    NSLog(@"统计 Test1ViewController action1 点击事件");

    },

    },

    @{

    @"EventDes":@"click action2",

    @"EventSelectorName":@"action2",

    @"block":^(idaspectInfo){

    NSLog(@"统计 Test1ViewController action2 点击事件");

    },

    }],

    },

    @"Test2ViewController":@{

    @"des":@"show Test2ViewController",

    }

    };

    [TraceManager setUpWithConfig:configDic];

    }

    @end

    TraceManager.m

    #import"TraceManager.h"

    @importUIKit;

    typedefvoid(^AspectHandlerBlock)(id aspectInfo);

    @implementationTraceManager

    + (void)setUpWithConfig:(NSDictionary*)configDic{

    // hook 所有页面的viewDidAppear事件

    [UIViewControlleraspect_hookSelector:@selector(viewDidAppear:)

    withOptions:AspectPositionAfter

    usingBlock:^(id 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*classNameinconfigDic) {

    Class clazz =NSClassFromString(className);

    NSDictionary*config = configDic[className];

    if(config[@"TrackEvents"]) {

    for(NSDictionary*eventinconfig[@"TrackEvents"]) {

    SEL selekor =NSSelectorFromString(event[@"EventSelectorName"]);

    AspectHandlerBlock block = event[@"block"];

    [clazz aspect_hookSelector:selekor

    withOptions:AspectPositionAfter

    usingBlock:^(id 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];

    其实做为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS学习交流群 651612063,不管你是小白还是大牛欢迎入驻,大家一起交流学习,加群私聊(小狄)就可以领取2018最全梳理的面试宝典和资料)。同时想要找工作的也可以私聊小编。

    需要资料的也可以加小编QQ2507362121。

    相关文章

      网友评论

        本文标题:iOS数据埋点统计方案(附Demo): 运行时Method Sw

        本文链接:https://www.haomeiwen.com/subject/ymbseftx.html