美文网首页iOS开发大圆满集合iOS 常规
iOS Method swizzling实现无侵入埋点

iOS Method swizzling实现无侵入埋点

作者: 陆离o | 来源:发表于2018-05-15 16:25 被阅读28次

    一.应用背景

    一般收集用户行为数据,埋点代码是在具体业务代码中实现,比如某个按钮的点击:

    - (void)btnClick:(UIButton *)sender{
        //埋点代码
        [MobClick event:@"Home_Back")];
    }
    

    项目初期这样写是没有问题的,但是随着项目迭代,运营统计需求的增加会导致埋点代码到处都是,不便于管理。于是就需要考虑新方案---无侵入埋点方案。

    二.无侵入埋点方案

    无侵入埋点的优点在于:

    • 埋点代码与业务代码剥离,降低耦合性
    • 埋点方法集中统一管理,减少漏埋点的几率

    三.实现原理

    方案.png

    从上图中可以看出实现步骤只有四步:

    1. 调用class_addMethod为需要hook的类增加新方法,用新的SEL指向原有Method的原始IMP
    2. 调用method_setImplementation为原有Method设置新IMP
    3. 在新IMP中调用新SEL,实现原IMP。并收集对象,参数等信息传出给具体埋点业务模块。
    4. 在埋点业务模块中,加入埋点业务逻辑。

    四.实现细节

    1. 新IMP的实现

    1.1 接收各种类型,数量的参数
    void setMethodImplement(Method m,HookType type){
    unsigned int count = method_getNumberOfArguments(m);
    if (count<2 && count>10) {
        return;
    }
    count = count-2;
    switch (count) {
        case 0:
            SetHookImplement(m, 0, type);
            break;
        case 1:
            SetHookImplement(m, 1, type);
            break;
            ...
            ...
        case 7:
            SetHookImplement(m, 7, type);
            break;
        case 8:
            SetHookImplement(m, 8, type);
            break;
        default:
            break;
    }
    }
    
    1.2 拼接函数
    #define SetHookImplement(m,num,type)\
    if(type==HookTypeNecessaryCallOrigin){\
    method_setImplementation(m,(IMP)hookFunWith##num##Params);\
    }else{\
    method_setImplementation(m,(IMP)hookFuncWith##num##ParamsWithOutCallOrigin);\
    }
    
    1.3 不同参数个数函数实现
    void *hookFunWith0Params(id self,SEL _cmd){
    return hookFunc(self,_cmd);}
    
    void *hookFunWith1Params(id self,SEL _cmd,void *param1){
    return hookFunc(self,_cmd,param1);}
    
    void *hookFunWith2Params(id self,SEL _cmd,void *param1,void *param2){
    return hookFunc(self,_cmd,param1,param2);}
    
    (省略)
    void *hookFunWith8Params(id self,SEL _cmd,void *param1,void *param2,void *param3,void *param4,void *param5,void *param6,void *param7,void *param8){
    return hookFunc(self,_cmd,param1,param2,param3,param4,param5,param6,param7,param8);} 
    
    1.4 新IMP(不定形参)
    void* hookFunc(id self, SEL _cmd,...){
    
    }
    

    2. 在新IMP的调用原始IMP

    2.1 根据新SEL生成方法签名,生成NSInvocation对象
     NSMethodSignature *methodSig = [self methodSignatureForSelector:hookSelect];
     if (methodSig == nil) {
         return nil;
     }
     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
    
    2.2 使用va_list获取新IMP的参数列表,遍历,并赋值到NSInvocation对象中
    id setInvocationParam(NSInvocation *invocation,int index,va_list *list,char *type){
    if(strcmp(type, @encode(id)) == 0){
        id param = va_arg(*list, id);
        [invocation setArgument:&param atIndex:index];
        return param;
    }
    if (strcmp(type, @encode(NSInteger)) == 0 ||
        strcmp(type, @encode(SInt8)) == 0 ||
        strcmp(type, @encode(SInt16)) == 0 ||
        strcmp(type, @encode(SInt32)) == 0 ||
        strcmp(type, @encode(BOOL)) == 0) {
        NSInteger param = va_arg(*list, NSInteger);
        [invocation setArgument:&param atIndex:index];
        return @(param);
    }
    if(strcmp(type, @encode(CGFloat)) == 0 ||
       strcmp(type, @encode(float)) == 0){
        CGFloat param = va_arg(*list, double);
        [invocation setArgument:&param atIndex:index];
    }
    
    if(strcmp(type, @encode(void(^)())) == 0){
        id param = va_arg(*list, id);
        [invocation setArgument:&param atIndex:index];
        return param;
    }
    
    (--省略--)
    return nil;
    }
    

    3. 埋点业务模块实现

    创建AppDelegate分类,在分类中调用Method swizzling模块,将plist文件中需hook的类,方法信息一次性传入。在block回调中处理埋点业务。


    hookPlist.png

    五.存在的问题

    1.va_list的问题

    很多人包括JSPatch作者指出“一开始是用 va_list 的方式获取参数,结果发现 arm64 下不可用,才发现arm64下 va_list 的结构改变了,导致无法上述这样取参数”,详见文章

    但上述方案目前在64位机器上运行正常,也在上线版本中运行正常,没有发生crash问题,原因是什么?

    2.父类实现问题

    比如需在一些vc中hook viewDidLoad方法,但该vc未实现,则需去父类找实现方法,可能会导致父类被多次hook,引发死循环。

    目前做法是强制被hook的类实现该方法,后续会参考Aspect,记录被hook状态,防止同一方法被多次hook。

    相关文章

      网友评论

        本文标题:iOS Method swizzling实现无侵入埋点

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