Objective-C Method Swizzling

来源:互联网 发布:linux 重启oracle 编辑:程序博客网 时间:2024/05/16 03:24

切入正题

Objective-C中的Hook又被称作 Method Swizzling ,这是动态语言都具有的特性。在Objective-C中经常会把Hook的逻辑写在 +load 方法中,有时候需要Hook子类和父类的同一个方法,但是它们的 +load 方法调用顺序不同。一个常见的顺序可能是:父类 ->子类 -> 子类类别 ->父类类别。所以Hook的顺序并不能保证,就不能保证Hook后方法调用的顺序是对的。而且使用不同方法Method Swizzing也会带来不同的结果。本文将会对这些情况下的Hook结果进行分析和总结。

Method Swizzling常用实现方案

方案A:如果类中没有实现Original selector对应的方法,那就先添加Method,并将其IMP映射为Swizzle的实现。然后替换Swizzle selector的IMP为Original的实现;否则交换二者IMP。

+(void)load {    static dispatch_once_t onceToken;    dispatch_once(&onceToken,^{        Class aClass = [self class];        SEL originalSelector = @selector(method_original:);        SEL swizzledSelector = @selector(method_swizzle:);        Method originalMethod = class_getInstanceMethod(aClass, originalSelector);        Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);        BOOL didAddMethod = class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));        if(didAddMethod) {            class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));        } else {            method_exchangeImplementations(originalMethod, swizzledMethod);        }    });}

有时为了避免方法命名冲突和参数_cmd被篡改,也会使用下面这种静态方法版本的Method Swizzle。CaptainHook中的宏定义也是采用这种方式,比较推荐:

typedef IMP *IMPPointer;static void MethodSwizzle(id self,SEL _cmd, id arg1);static void (*MethodOriginal)(id self,SEL _cmd, id arg1);static void MethodSwizzle(id self, SEL _cmd, id arg1) {    MethodOriginal(self,_cmd,arg1);}BOOL class_swizzleMethodAndStore(Class class, SEL original,IMP replacement, IMPPointer store) {    IMP imp = NULL;    Method method = class_getInstanceMethod(class, original);    if(method) {        const char *type = method_getTypeEncoding(method);        imp = class_replaceMethod(class, original, replacement, type);        if(!imp) {            imp = method_getImplementation(method);        }    }    if(imp && store) {        *store = imp;    }    return (imp != NULL);}+(BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {    return class_swizzleMethodAndStore(self, original, replacement, store);}+(void)load {    [self swizzle:@selector(originalMethod:) with:(IMP)MethodSwizzle store:(IMP *)&MethodOriginal];}

方案B
实质对方案A的缩减版:

+(void)load {    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        Class aClass = [self class];        SEL originalSelector = @selector(method_original:);        SEL swizzledSelector = @selector(method_swizzle:);        Method originalMethod = class_getInstanceMethod(aClass, originalSelector);        Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);        method_exchangeImplementations(originalMethod, swizzledMethod);    });}

直接交换IMP是很危险的。因为如果这个类中没有实现这个方法,class_getInstanceMethod()返回的是某个类的Method对象,这样method_exchangeImplementations()就把父类的原始方法实现(IMP)跟这个类的Swizzle实现交换。这样其他父类与其他子类的方法调用就会出现问题,最严重的就是crash。

0 0
原创粉丝点击