切入正题
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。