runtime中的交換方法method_exchangeImplementations存在的問題

  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è)圖:


image.png

簡(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
這篇文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容