Method method1 = class_getInstanceMethod(self, @selector(eat));
Method method2 = class_getInstanceMethod(self, @selector(anotherEat));
method_exchangeImplementations(method1, method2);
先看下上面這段簡(jiǎn)單的方法替換代碼。
方法替換到底做了什么?
上面的代碼首先是通過方法名獲取了兩個(gè)方法,然后將兩個(gè)方法的實(shí)現(xiàn)替換了。
方法的底層結(jié)構(gòu)
struct method_t {
SEL method_name
IMP method_imp
char * types
}
我們知道在類對(duì)象和元類對(duì)象中都有一個(gè)方法列表,分別存儲(chǔ)著實(shí)例方法和類方法,存儲(chǔ)著的方法底層結(jié)構(gòu)如上面代碼所示,它是一個(gè)結(jié)構(gòu)體,在結(jié)構(gòu)體中有三個(gè)參數(shù),他們的含義分別是:
method_name:方法的名字
method_imp:保存著一個(gè)指針,指向了函數(shù)的具體實(shí)現(xiàn)。實(shí)際方法替換真正替換的是method_imp即方法的具體實(shí)現(xiàn)。
types:能代表這個(gè)方法的字符,用法可以忽略。
直接使用method_exchangeImplementations方法的問題
我們?cè)倏纯粗苯邮褂胢ethod_exchangeImplementations方法進(jìn)行方法的替換有什么問題呢?
假設(shè)有一個(gè)Father類,有一個(gè)繼承自Father類的Son類,F(xiàn)ather類中有一個(gè)eat方法,Son類中沒有重寫eat方法,現(xiàn)在在Son的一個(gè)分類中對(duì)Son的eat方法進(jìn)行替換。
看代碼:
+ (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(@"替換之后的吃的方法...");
}
創(chuàng)建Father、Son的實(shí)例并調(diào)用實(shí)例方法
Son * s = [Son new];
[s eat];
Father * f = [Father new];
[f eat];
打?。?/p>
runtime的兩個(gè)方法替換[18967:1520161] self:<Son: 0x281749590>
runtime的兩個(gè)方法替換[18967:1520161] 爸爸吃東西...
runtime的兩個(gè)方法替換[18967:1520161] 替換之后的吃的方法...
runtime的兩個(gè)方法替換[18967:1520161] self:<Father: 0x2817491e0>
runtime的兩個(gè)方法替換[18967:1520161] -[Father anotherEat]: unrecognized selector sent to instance 0x2817491e0
程序發(fā)生了崩潰,報(bào)的錯(cuò)是在Father類中找不到anotherEat方法。Son實(shí)例調(diào)用eat方法是完全沒有問題的。我們先分析一下Son。
分析Son
當(dāng)在Son的分類中替換了Son的eat和anotherEat兩個(gè)方法的實(shí)現(xiàn)后,Son實(shí)例調(diào)用eat實(shí)際調(diào)用的是anotherEat方法的具體實(shí)現(xiàn),調(diào)用anotherEat實(shí)際調(diào)用的是eat方法的具體實(shí)現(xiàn)。anotherEat方法名在Son的類對(duì)象列表中能找到,所以是沒問題的。
再來分析Father
分析Father
Father類中的eat方法的具體實(shí)現(xiàn)(imp)被替換成了anotherEat方法的具體實(shí)現(xiàn),當(dāng)調(diào)用Father實(shí)例的eat方法時(shí)就會(huì)走anotherEat方法的具體實(shí)現(xiàn),如果anotherEat方法中不調(diào)用anotherEat方法也是沒有問題的,但如果調(diào)用了anotherEat(如放數(shù)組越界中還是要調(diào)用系統(tǒng)的方法的),由于Father類對(duì)象中的方法列表中并沒有名字為anotherEat的這個(gè)方法,所以報(bào)了找不到這個(gè)方法的錯(cuò)誤。
盜用別人的一個(gè)圖:

簡(jiǎn)單一句話:
替換了父類某個(gè)方法的具體實(shí)現(xiàn),但并沒有改變?cè)摲椒ǖ姆椒?,從而可能?dǎo)致崩潰。
那么我們要想替換兩個(gè)方法的實(shí)現(xiàn)同時(shí)避免出現(xiàn)以上問題該怎么辦呢?這個(gè)時(shí)候就需要我們利用runtime的動(dòng)態(tài)解析即動(dòng)態(tài)添加方法,請(qǐng)看runtime中的交換方法class_replaceMethod
這篇文章