Method method1 = class_getInstanceMethod(self, @selector(eat));
Method method2 = class_getInstanceMethod(self, @selector(anotherEat));
method_exchangeImplementations(method1, method2);
先看下上面这段简单的方法替换代码。
方法替换到底做了什么?
上面的代码首先是通过方法名获取了两个方法,然后将两个方法的实现替换了。
方法的底层结构
struct method_t {
SEL method_name
IMP method_imp
char * types
}
我们知道在类对象和元类对象中都有一个方法列表,分别存储着实例方法和类方法,存储着的方法底层结构如上面代码所示,它是一个结构体,在结构体中有三个参数,他们的含义分别是:
method_name:方法的名字
method_imp:保存着一个指针,指向了函数的具体实现。实际方法替换真正替换的是method_imp即方法的具体实现。
types:能代表这个方法的字符,用法可以忽略。
直接使用method_exchangeImplementations方法的问题
我们再看看直接使用method_exchangeImplementations方法进行方法的替换有什么问题呢?
假设有一个Father类,有一个继承自Father类的Son类,Father类中有一个eat方法,Son类中没有重写eat方法,现在在Son的一个分类中对Son的eat方法进行替换。
看代码:
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method method1 = class_getInstanceMethod(self, @selector(eat));
Method method2 = class_getInstanceMethod(self, @selector(anotherEat));
method_exchangeImplementations(method1, method2);
});
}
- (void)anotherEat{
NSLog(@"self:%@", self);
[self anotherEat];
NSLog(@"替换之后的吃的方法...");
}
创建Father、Son的实例并调用实例方法
Son * s = [Son new];
[s eat];
Father * f = [Father new];
[f eat];
打印:
runtime的两个方法替换[18967:1520161] self:<Son: 0x281749590>
runtime的两个方法替换[18967:1520161] 爸爸吃东西...
runtime的两个方法替换[18967:1520161] 替换之后的吃的方法...
runtime的两个方法替换[18967:1520161] self:<Father: 0x2817491e0>
runtime的两个方法替换[18967:1520161] -[Father anotherEat]: unrecognized selector sent to instance 0x2817491e0
程序发生了崩溃,报的错是在Father类中找不到anotherEat方法。Son实例调用eat方法是完全没有问题的。我们先分析一下Son。
分析Son
当在Son的分类中替换了Son的eat和anotherEat两个方法的实现后,Son实例调用eat实际调用的是anotherEat方法的具体实现,调用anotherEat实际调用的是eat方法的具体实现。anotherEat方法名在Son的类对象列表中能找到,所以是没问题的。
再来分析Father
分析Father
Father类中的eat方法的具体实现(imp)被替换成了anotherEat方法的具体实现,当调用Father实例的eat方法时就会走anotherEat方法的具体实现,如果anotherEat方法中不调用anotherEat方法也是没有问题的,但如果调用了anotherEat(如放数组越界中还是要调用系统的方法的),由于Father类对象中的方法列表中并没有名字为anotherEat的这个方法,所以报了找不到这个方法的错误。
盗用别人的一个图:
简单一句话:
替换了父类某个方法的具体实现,但并没有改变该方法的方法名,从而可能导致崩溃。
那么我们要想替换两个方法的实现同时避免出现以上问题该怎么办呢?这个时候就需要我们利用runtime的动态解析即动态添加方法,请看runtime中的交换方法class_replaceMethod
这篇文章