OC交换方法

title: OC交换方法
date: 2017-08-27
tags: hook,method swizzling,埋点

OC交换方法

需求:

  • 项目中要做无侵入式埋点
  • 项目中要拦截某些函数,而让其执行特定代码后,不再执行原函数(如在做项目页面路由时,所有由其他app(或网页)唤起本app的页面,都用window.rootViewController present一个navigationController来呈现,nav的rootVC就是要呈现的这个界面,所以需要hook这个VC的返回按钮函数,当次VC不是nav的rootVC时,用pop返回,当是rootVC时用dismiss返回)
  • 让所有被hook的函数都指向同一个函数

源码和使用说明地址

初始化

hook原函数,并需要调用原函数

-(void)setRecordDic:(NSDictionary *)recordDic
   andHookBlock:(void (^)(NSString *target,
                          NSString *action,
                          NSDictionary *handleDic,
                          NSDictionary *params))handleBlock{

_recordDic = recordDic;
self.hookBlock = handleBlock;
dispatch_async(_serialQueue, ^{
    NSArray *allKeys = self.recordDic.allKeys;
    //读出字典中需要hook的类
    for (NSString *className in allKeys) {
        NSDictionary *actionDic = self.recordDic[className];
        Class classInstance = NSClassFromString(className);
        NSArray *actionKeys = actionDic.allKeys;
        //读出该类需要hook的方法
        for (NSString *actionName in actionKeys) {
            Method originMethod = class_getInstanceMethod(classInstance, NSSelectorFromString(actionName));
            //hook实例方法
            if (originMethod) {
                Class classImplenmentThisInstanceMethod = getClassThatImplementThisMethod(classInstance, originMethod);
                class_addMethod(classImplenmentThisInstanceMethod,
                                NSSelectorFromString([NSString stringWithFormat:@"bnrHook_%@",actionName]),
                                method_getImplementation(originMethod),
                                method_getTypeEncoding(originMethod));
                method_setImplementation(originMethod, (IMP)hookFunc);
            }else{
            //hook类方法
                originMethod = class_getClassMethod(classInstance, NSSelectorFromString(actionName));
                if (originMethod) {
                    Class metaClass = objc_getMetaClass(class_getName(classInstance));
                    Class metaClassImplenmentThisClassMethod = getClassThatImplementThisMethod(metaClass,originMethod);
                    class_addMethod(metaClassImplenmentThisClassMethod,
                                    NSSelectorFromString([NSString stringWithFormat:@"bnrHook_%@",actionName]),
                                    method_getImplementation(originMethod),
                                    method_getTypeEncoding(originMethod));
                    method_setImplementation(originMethod, (IMP)hookFunc);
                }
                
            }   
        }    
    }
});
}


/**
 *  在subClass的原型链上找最靠近该类的父类实现了这个实例方法,
 */
Class getClassThatImplementThisMethod(Class subClass,Method m){
    Class classImplenmentThisInstanceMethod = subClass;
    while (classImplenmentThisInstanceMethod) {
        unsigned int count  = 0;
        Method *methods =  class_copyMethodList(classImplenmentThisInstanceMethod, &count);
   
    //函数地址表有序排列,直接比较地址大小,不遍历
    if (count >0) {
        //因为hook的时候,给class加了一些以bnrHook或者hnrHookOrigin开头的函数
        //要除去这些函数,地址分布才是线性的
        unsigned int start = 0;
        while (start < count) {
            SEL mm = method_getName(methods[start]);
            NSString *mmName = NSStringFromSelector(mm);
            if ([mmName containsString:@"bnrHook"]||
                [mmName containsString:@"bnrHookOrigin"]) {
                start++;
            }else{
                break;
            }
        }
        if (m >= methods[start]&&
            m <= methods[count-1]) {
            return classImplenmentThisInstanceMethod;
        }
    }

    classImplenmentThisInstanceMethod = class_getSuperclass(classImplenmentThisInstanceMethod);
}
return classImplenmentThisInstanceMethod;
}

如A B C D类为依次继承关系,A C实现了func方法,B D未实现func

hook B的func方法,实际上是要是让A的func指向hookFunc,并给A加上一个bnrHook_func方法,让其指向A的func具体实现,如用代码class_addMethod([B class], NSSelectorFromString([NSString stringWithFormat:@"bnrHook_%@",actionName]), method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
实际上是给B加了一个bnrHook_func,调用A类的func时,先调用了hookFunc的实现,确找不到A上的bnrHook_func的实现了;

hook C的func函数时,需要交换的C上的func的实现

hook D的func函数时,需要交换的是C上的func的实现

getClassThatImplementThisMethod这个函数就是处理这些情况,找出具体应该交换哪个类上的方法;

hook原函数,并不需要再调用原函数

初始化方法同上,按照这里的目的,只要让原函数指向hook后的实现就可以了,并不需要给被hook的类增加函数,让其指向原来的实现;但这里有一些情况就不能满足了,比如 A B类为依次继承关系,A实现了func,B未实现func。想拦截B的func方法,因为B没有实现该方法,那实际上hook的时候,是把A的func指向了hookFuncWithOutCallOriginFunc的实现了,当调用A的func时,我们希望不拦截他,希望调用原来的实现(因为B为实现func,不拦截他做不了,能做的是在调用时,不调用用户传入的block,而去调用func原来的实现),基于这点,还是需要往A上去增加方法,让其指向原来的实现,并在hook后,调用它。

hook函数的具体实现

/**
*  所有需要hook并需要调用源函数的函数都hook到了这个方法
*/
void* hookFunc(id self, SEL _cmd,...){
BNRHookAction *record = [BNRHookAction shareInstance];
NSString *className = NSStringFromClass([self class]);
NSDictionary *actionDic = record.recordDic[className];
NSString *actionName = NSStringFromSelector(_cmd);
NSDictionary *actionInfo = actionDic[actionName];
//hook表里面存放的是父类名字和父类action,那么子类调用这个action时,
//需要把父类的action下的字典丢出去
if (actionInfo == nil) {
    Class fatherClass = [self class];
    while (fatherClass) {
        Class tmpClass = class_getSuperclass(fatherClass);
        NSDictionary *tmpActionDic = record.recordDic[NSStringFromClass(tmpClass)];
        actionInfo = tmpActionDic[actionName];
        if (actionInfo == nil) {
            fatherClass = tmpClass;
        }else{
            break;
        }
    }
}

SEL hookSelect = NSSelectorFromString([NSString stringWithFormat:@"bnrHook_%@",NSStringFromSelector(_cmd)]);

NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"self"] = self;
params[@"_cmd"] = NSStringFromSelector(_cmd);
if ([self respondsToSelector:hookSelect]) {
    
    NSMethodSignature *methodSig = [self methodSignatureForSelector:hookSelect];
    if (methodSig == nil) {
        return nil;
    }
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
    
    Method originMethod = class_getInstanceMethod([self class], _cmd);
    if (originMethod == nil) {
        originMethod = class_getClassMethod(self, _cmd);
    }
    unsigned int argsCount = method_getNumberOfArguments(originMethod);
    va_list args;
    va_start(args, _cmd);
    
    
    for (int i = 2; i < argsCount; i++) {
        char *paramType = method_copyArgumentType(originMethod, i);
        id param = setInvocationParam(invocation, i, args, paramType);
        params[[NSString stringWithFormat:@"%@",@(i-2)]] = param;
        
    }
    va_end(args);
    if (actionInfo) {
        !record.hookBlock?:record.hookBlock(className,actionName,actionInfo,params.copy);
    }
    [invocation setTarget:self];
    [invocation setSelector:hookSelect];
    [invocation invoke];
    
    char *returnType = method_copyReturnType(originMethod);
    return getInvocationReturnValue(invocation,returnType);
    
    }
    return nil;
}

函数第二个(从0开始)参数是一个不定形参,函数的返回值是void *类型,被hook的函数的参数个数,类型和返回值都是不确定的,在代码实现中需解析出不定形参的个数和具体的值,并通过NSInvocation来调用原来的函数实现,得到返回值。

备注

不支持为父类和子类同时hook同一个函数(如A是父类,A1和A2是子类,A1实现了viewWillAppear:,A2未实现,同时hook A的viewWillAppear:和A1的viewWillAppear:会造成死循环; 同时hook A1和A2的viewWillAppear:也会造成调用的死循环,其实A2未实现viewWillAppear:,hook A2时,就是hook的A的viewWillAppear:. 如果子类不调用[super method]是不会造成调用死循环。)

hook函数,并需要调用原函数时,请尽量保证hook的类有实现要hook的action,而不是父类实现了这个action.

你可能感兴趣的:(OC交换方法)