iOS Method Swizzling的实现
因为Objective-C的runtime机制, Method Swizzling这个黑魔法解决了我们实际开发中诸多常规手段所无法解决的问题, 比如代码的插桩,Hook,Patch等等。最近查看AFNetworking源码中就使用了Method Swizzling。
Method Swizzling常用的方法
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );
// 获取所有方法的List
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
// 备注:当判断一个实例方法是否实现时,第一个参数要用类对象,也就是[Person class]。
当判断一个类方法是否实现时,第一个参数要传元类,也就是object_getClass([Person class])。
场景一
我们经常看到在UIViewController中会在不改变原代码的情况下,添加一些新的事件,比如记录每次进入的ViewController,这时候我们可以创建一个UIViewControler的Category
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 在ViewController中添加方法,class_addMethod这个方法的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
//如果要修改已存在实现,可以使用class_replaceMethod或者更深一步使用method_setImplementation,
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
// 我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。
// 进行方法实现的交换
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
// 这里不会产生递归调用的
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
系统调用UIViewController的viewWillAppear方法时,实际上执行的是我们实现的xxx_viewWillAppear方法。而我们在xxx_viewWillAppear方法内部调用[self swizzlingViewDidLoad];时,执行的是UIViewController的viewWillAppear方法。
场景二 不同类的方法替换
例如已知一个className为Car的类中有一个实例方法,已知有一个MyCar类,这两个类没有继承等关系。
Car
Car.h
import <Foundation/Foundation.h>
@interface Car : NSObject
- (void)run;
@end
Car.m
@implementation Car
- (void)run {
NSLog(@"Car RUN");
}
@end
MyCar
MyCar.h
@interface MyCar : NSObject
- (void)dy_run;
@end
Car.m
#import "MyCar.h"
#import <objc/runtime.h>
@implementation MyCar
+ (void)load {
Class originalClass = NSClassFromString(@"Car");
Class swizzledClass = [self class];
SEL originalSelector = NSSelectorFromString(@"run");
SEL swizzledSelector = @selector(dy_run);
// car
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
// mycar
Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
// 替换给定的originalClass中的方法 originalSelector 的实现
class_replaceMethod(originalClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
}
- (void)dy_run {
NSLog(@"dy_run");
}
- 使用 class_replaceMethod(originalClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
具体调用的使用
Car *car = [[Car alloc] init];
[car run];
// [car performSelector:NSSelectorFromString(@"dy_run")];
MyCar *myCar = [[MyCar alloc] init];
[myCar dy_run];
打印结果
2019-03-19 09:46:33.250563 AppDemo[4487:874286] dy_run
2019-03-19 09:46:33.250640 AppDemo[4487:874286] dy_run
由此看出,Car调用run方法,打印了dy_run。那class_replaceMethod就是替换给定的originalClass中的方法 originalSelector 的实现。
- 那如果将class_replaceMethod换成method_exchangeImplementations
class_replaceMethod(originalClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
将这行代码改成以下的
// 方法实现交换
method_exchangeImplementations(originalMethod, swizzledMethod);
当使用这个method_exchangeImplementations方法的时候
打印结果
2019-03-19 10:47:47.132487 AppDemo[4627:899522] dy_run
2019-03-19 10:47:47.132533 AppDemo[4627:899522] Car RUN
可以看到Car调用run方法,打印了dy_run;相应的MyCar调用dy_run,打印了Car RUN。
method_exchangeImplementations将两个方法实现交换
class_addMethod
- 当使用这个class_addMethod方法的时候,
Class originalClass = NSClassFromString(@“Car”);
BOOL registerMethod = class_addMethod(originalClass, swizzledSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
这时候将Car类中新添加了一个方法dy_run。这时候可以使用 [car performSelector:NSSelectorFromString(@“dy_run”)];进行调用
打印结果
2019-03-19 10:53:12.182487 AppDemo[4627:899522] dy_run
这时候Car类中就有了一个dy_run方法了,Car类对象就可以调用了。