[iOS開(kāi)發(fā)]runtime交換某個(gè)對(duì)象的兩個(gè)方法

OC是一門(mén)動(dòng)態(tài)語(yǔ)言,方法的調(diào)用本質(zhì)上是利用objc_msgSend進(jìn)行"發(fā)消息",也就是某類(lèi)或某對(duì)象調(diào)用其某方法,本質(zhì)上是向某個(gè)對(duì)象的指針發(fā)送了一條消息,在此之前方法和對(duì)象(或類(lèi))都沒(méi)有真正確定下來(lái)即動(dòng)態(tài)綁定,消息與方法的真正實(shí)現(xiàn)是在執(zhí)行階段綁定的,而非編譯階段.

所謂動(dòng)態(tài)綁定,我舉一個(gè)簡(jiǎn)單的C語(yǔ)言例子

#import <stdio.h> 

void Chinese() {  
    printf("Chinese book");  
}  
void Math() {  
    printf("Math book");  
}  

void doTheThing(int type) {  
    if (type == 0) {  
        Chinese();  
    } else {  
        Math();  
    }  
    return 0;  
}

對(duì)于上面這一類(lèi)型到底是調(diào)用hello函數(shù)還是goodbye函數(shù),這兩個(gè)函數(shù)都是已經(jīng)確定的,就像是有一本語(yǔ)文書(shū)一本數(shù)學(xué)書(shū)放在你面前,而你已經(jīng)知道這兩本中的一本最終會(huì)被你拿到手上,語(yǔ)文書(shū)和數(shù)學(xué)書(shū)已經(jīng)是放在那里的了,不是動(dòng)態(tài)改變啊的.

#import <stdio.h> 

void Chinese() {  
    printf("Chinese book");  
}  
void Math() {  
    printf("Math book");  
}  

void doTheThing(int type) {  
    void (*func)();  
    if (type == 0) {  
        func = Chinese;  
    } else {  
        func = Math;  
    }  
    func();  
    return 0;  
}

而對(duì)于這一種類(lèi)型,我們可以看到我們聲明了一個(gè)函數(shù)指針func,就好比你拿到了夠買(mǎi)其中一本書(shū)的錢(qián),只是錢(qián)在你手里,到底是買(mǎi)語(yǔ)文書(shū)還是數(shù)學(xué)書(shū)還沒(méi)有確定,手上的錢(qián)是會(huì)動(dòng)態(tài)改變的,這叫做動(dòng)態(tài)綁定.

當(dāng)向一個(gè)對(duì)象發(fā)送消息時(shí),objc_msgSend方法根據(jù)對(duì)象的isa指針找到對(duì)象的類(lèi),然后在類(lèi)的調(diào)度表(dispatch table)中查找selector。如果無(wú)法找到selector,objc_msgSend通過(guò)指向父類(lèi)的指針找到父類(lèi),并在父類(lèi)的調(diào)度表(dispatch table)中查找selector,以此類(lèi)推直到NSObject類(lèi)。一旦查找到selector,objc_msgSend方法根據(jù)調(diào)度表的內(nèi)存地址調(diào)用該實(shí)現(xiàn)。 通過(guò)這種方式,message與方法的真正實(shí)現(xiàn)在執(zhí)行階段才綁定。

為了保證消息發(fā)送與執(zhí)行的效率,系統(tǒng)會(huì)將全部selector和使用過(guò)的方法的內(nèi)存地址緩存起來(lái)。每個(gè)類(lèi)都有一個(gè)獨(dú)立的緩存,緩存包含有當(dāng)前類(lèi)自己的 selector以及繼承自父類(lèi)的selector。查找調(diào)度表(dispatch table)前,消息發(fā)送系統(tǒng)首先檢查receiver對(duì)象的緩存。

我們現(xiàn)在看一下objc_msgSend如何使用

先在控制器中定義一個(gè)方法

- (void)changeColor:(UIColor *)colorOne colorTwo:(UIColor *)colorTwo colorThree:(UIColor *)colorThree colorFour:(UIColor *)colorFour{
    static NSUInteger count = 0;
    NSUInteger k = count %4 ;
    switch (k) {
        case 0:
            self.view.backgroundColor = colorOne;
            break;
        case 1:
            self.view.backgroundColor = colorTwo;
            break;
        case 2:
            self.view.backgroundColor = colorThree;
            break;
        case 3:
            self.view.backgroundColor = colorFour;
            break;
        default:
            break;
    }
    count ++;
}

然后對(duì)其進(jìn)行調(diào)用

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    SEL change = @selector(changeColor:colorTwo:colorThree:colorFour:);
    UIColor *colorOne = [UIColor blueColor];
    UIColor *colorTwo = [UIColor greenColor];
    UIColor *colorThree = [UIColor redColor];
    UIColor *colorFour = [UIColor yellowColor];
//  這里到底有幾個(gè)參數(shù)你就放幾個(gè)id,當(dāng)然你也可以直接指定類(lèi)型
      ((void(*)(id,SEL, id,id,id,id))objc_msgSend)(self,change , colorOne, colorTwo,colorThree,colorFour);
}

交換兩個(gè)方法

在實(shí)際開(kāi)發(fā)中我們會(huì)遇到這樣一個(gè)問(wèn)題,當(dāng)項(xiàng)目開(kāi)發(fā)得差不多的時(shí)候,或者說(shuō)到了項(xiàng)目迭代的時(shí)候,我們發(fā)現(xiàn)了內(nèi)存泄露(如block的不規(guī)范使用導(dǎo)致),不知道到底是哪個(gè)View或者說(shuō)是哪個(gè)控制器沒(méi)有正常被回收.那么我們常用的做法就是在- (void)dealloc方法中打印某些字樣,去控制臺(tái)看到底是在哪些界面跳轉(zhuǎn)或者回跳的時(shí)候哪些對(duì)象沒(méi)有調(diào)用dealloc方法.

在這個(gè)時(shí)候我們要是去一個(gè)個(gè)文件中重寫(xiě)dealloc方法就太繁瑣了,而且容易漏掉一些類(lèi).面對(duì)這種情景,使用分類(lèi),在分類(lèi)中交換方法是最好的解決辦法,而且它的實(shí)現(xiàn)不需要引入分類(lèi)頭文件

#import "UIView+dealloc.h"
#import <objc/runtime.h>
@implementation UIView (dealloc)

+ (void)load{
    Method m2 = class_getInstanceMethod([self class], @selector(myDealloc));
    Method m1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
    method_exchangeImplementations(m2, m1);
}
//系統(tǒng)調(diào)用dealloc方法的時(shí)候會(huì)調(diào)用該方法
- (void)myDealloc{
    NSLog(@"%@掛了", self);
//此刻實(shí)際是在調(diào)用dealloc方法
    [self myDealloc];   
}

我這里寫(xiě)了一個(gè)UIView的分類(lèi),也就是說(shuō)所有UIView的子類(lèi)被回收的時(shí)候都能夠調(diào)用這個(gè)犯法,其中+ (void)load 方法會(huì)被調(diào)用一次,它并不需要該文件被使用才會(huì)被調(diào)用,也就是在能內(nèi)存中加載的時(shí)候就會(huì)被調(diào)用,且僅有一次,在這一次調(diào)用中我們把UIView的兩個(gè)對(duì)象方法進(jìn)行了替換(也就是在最早的時(shí)候就交換了方法,相當(dāng)于把這兩條神經(jīng)給交換接上了).

以上就是一個(gè)經(jīng)常遇到的的runtime替換兩個(gè)方法的使用場(chǎng)景,若有寫(xiě)的不好的地方歡迎指出.

版權(quán)聲明:本文版權(quán)歸本文作者所有,始發(fā)于簡(jiǎn)書(shū),如需轉(zhuǎn)載請(qǐng)聯(lián)系作者,違者必究.

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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