iOS的Method Swizzling是一个非常有意思的run time应用案例.用它可以实现AOP,也可以用来hook很多API,进行很多hack的操作
相关资料很多,总体来讲就是调换两个Method的Imp,也就是调换函数指针.在很多AOP的案例中,使用这种方式hook住关键方法,在递归调用方法前/后实现AOP.例如:在一个UIViewController的category中,可以这样
+ (void)load {
Method currentDidLoadMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoadAOP));
Method oriDidLoadMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
method_exchangeImplementations(currentDidLoadMethod, oriDidLoadMethod);
}
- (void)viewDidLoadAOP {
//在这里写代码,可以类似成为Before
[self viewDidLoadAOP];//这里递归调用自己
//在这里写代码,可以类似成为After
但是如果交换不同类之间的方法,这样调用就会crash.
比如有A,B两个类,分别有a方法和b方法.我们调用A类的a方法,希望通过method swizzling对A方法进行处理.
调用A类的a方法
A *aClass = [A new];
[aClass a];
a方法
- (void)a {
NSLog(@"a");
}
b方法(我们希望增强a方法,所以递归调用)
- (void)b {
[self b];//递归调用
NSLog(@"b");
}
这样就会导致crash,报错为A类中找不到b这个selector.
原因是当执行[aClass a]的时候,因为交换Imp的原因,则会进入到b方法中.而b中此时的self,是指的A,因为交换仅仅改变Imp,receiver和selector都不会改变,A类中没有b这个方法,当然会crash.
解决方案是:
如果需要在不同的类中交换方法,一定要注意此时的self指的是什么.如果需要调用self没有的方法,那么使用class_addMethod将不存在的方法add进去即可.
在不同类之间实现Method Swizzling
先举个例子:已知一个className为 Car 的类中有一个实例方法 - (void)run:(double)speed , 目前需要Hook该方法对速度小于120才执行run的代码, 按照方法交换的流程, 代码应该是这样的:
#import <objc/runtime.h>
@interface MyCar : NSObject
@end
@implementation MyCar
+ (void)load {
Class originalClass = NSClassFromString(@"Car");
Class swizzledClass = [self class];
SEL originalSelector = NSSelectorFromString(@"run:");
SEL swizzledSelector = @selector(xxx_run:);
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
// 向Car类中新添加一个xxx_run:的方法
BOOL registerMethod = class_addMethod(originalClass,
swizzledSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (!registerMethod) {
return;
}
// 需要更新swizzledMethod变量,获取当前Car类中xxx_run:的Method指针
swizzledMethod = class_getInstanceMethod(originalClass, swizzledSelector);
if (!swizzledMethod) {
return;
}
// 后续流程与之前的一致
BOOL didAddMethod = class_addMethod(originalClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(originalClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- (void)xxx_run:(double)speed {
if (speed < 120) {
[self xxx_run:speed];
}
}
@end
与之前的流程相比,在前面添加了两个逻辑:
- 利用runtime向目标类 Car 动态添加了一个新的方法,此时 Car 类与 MyCar 类一样具备了 xxx_run: 这个方法, MyCar 的利用价值便结束了;
- 为了完成后续 Car 类中 run: 与 xxx_run: 的方法交换,此时需要更新swizzledMethod变量为 Car 中的 xxx_run: 方法所对应的Method.
以上所有的逻辑也可以合并简化为以下:
+ (void)load {
Class originalClass = NSClassFromString(@"Car");
Class swizzledClass = [self class];
SEL originalSelector = NSSelectorFromString(@"run:");
SEL swizzledSelector = @selector(xxx_run:);
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
IMP originalIMP = method_getImplementation(originalMethod);
IMP swizzledIMP = method_getImplementation(swizzledMethod);
const char *originalType = method_getTypeEncoding(originalMethod);
const char *swizzledType = method_getTypeEncoding(swizzledMethod);
class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
}
简化后的代码便与之前使用Category的方式并没有什么差异, 这样代码就很容易覆盖到这两种场景了, 但我们需要明确此时 class_replaceMethod 所完成的工作却是不一样的.
- 第一个 class_replaceMethod 直接在 Car 类中注册了 xxx_run: 方法, 并且指定的IMP为当前 run: 方法的IMP;
- 第二个 class_replaceMethod 与之前的逻辑一致, 当 run: 方法是实现在 Car 类或 Car 的父类, 分别执行 method_setImplementation 或 class_addMethod ;
参考资料:http://ju.outofmemory.cn/entry/272003
参考资料:http://www.jianshu.com/p/84cfd554356d