iOS探索:Runtime之消息轉發及動態添加方法

在開始以前,咱們先來了解下OC中的類與對象git

268805-196560ee064edb09..jpg

這是一張經典的類的關係示意圖,接下來簡單的介紹一下這張圖github

  • 首先當咱們建立一個實例對象,會拷貝這個實例對象所屬類的成員變量,可是不會拷貝類定義的方法緩存

  • 當咱們發送消息給實例對象時,會經過這個實例對象中的isa指針去找到它對應的類,在類的方法緩存中先去尋找,若是沒有命中,那麼會去方法列表中尋找,若是仍是沒有命中,會經過類中的super class指針去它的父類中尋找,按照一樣的流程一直向上查找,直至找到根類也就是NSObject,這其中須要注意的是NSObject中super class指針指向的是nilbash

  • 說完實例對象,那麼類對象接收到消息時是怎樣去查找對應方法的呢?一樣的,在類對象中也會isa指針,它指向的是類對象所對應的元類,也是先在元類的方法緩存中去尋找,若是沒有命中,那麼會去方法列表中尋找,要是尚未命中,會經過元類中的super class指針去它的父元類中尋找,按照一樣的流程一直向上查找,直至找到根元類,這裏有一個須要注意的點是在元類中,每個元類的isa指針都指向根元類,而且根元類的super class指針指向的是根類(NSObject),也就是說當一個類的某個類方法沒有實現,可是在根類中卻有同名的實例方法實現,這個時候就會調用這個同名的實例方法數據結構

我的認爲只要將這張圖理解透徹就能夠很好的理解類與對象的關係函數

吃過了開胃菜,接下來咱們進入正餐post

消息轉發

出來吧,流程圖!ui

WX20181212-170746@2x.png

  • 對於實例方法,系統首先會回調resolveInstanceMethod:這個方法,這個方法的參數是一個選擇器(SEL),返回值類型是BOOL類型的,告訴系統,咱們要不要解決這個實例方法的實現,若是返回是YES,那麼消息已處理,若是返回是NO,這個時候系統會給與咱們第二次處理消息的機會spa

  • 系統會會調用forwardingTargetForSelector:這個方法,這個方法的參數是一個選擇器(SEL),返回值是一個id類型,至關於告訴系統這個選擇器是具體有哪一個對象來處理,若是咱們指定了一個轉發目標,系統會把轉發消息給咱們的轉發目標,同時會結束當前的轉發流程,若是在第二次機會中咱們依舊沒有給返回一個轉發目標,這個時候系統會給與咱們第三次處理消息的機會,也是最後一次機會3d

  • 系統會調用methodSignatureForSelector:這個方法,這個方法的參數是一個選擇器(SEL),方法的返回值是一個methodSignature,這個方法簽名其實是對這個方法選擇器的類型,返回值和參數的一個封裝,此時,若是返回了一個方法簽名,系統會接着調用forwardInvocation:方法,若是可以處理的話那麼消息已處理,若是說methodSignatureForSelector:返回空,或者說forwardInvocation:沒法處理,那麼會被標記爲消息沒法處理,平時咱們常見的一中crash就是沒法識別選擇器,其實就是走到了最後一步仍是沒法處理

下面上代碼
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    //若是調用的使咱們的test方法
    if (sel == @selector(test)) {
        NSLog(@"resolveInstanceMethod");
        return NO;
    }else {
        return [super resolveInstanceMethod:sel];
    }
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    
    NSLog(@"forwardingTargetForSelector");
    //返回nil,這樣就能走到第三個步驟
    return nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    if (aSelector == @selector(test)) {
        NSLog(@"methodSignatureForSelector");
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }else {
        return [super methodSignatureForSelector:aSelector];
    }
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    NSLog(@"forwardInvocation");
}
複製代碼
  • 首先咱們建立一個類繼承自NSObject,在這個類中,咱們聲明一個test方法,可是並不實現,而後咱們實現消息轉發的方法

  • 首先在resolveInstanceMethod:方法中,若是這個選擇器是test,咱們打印一下這個方法的名字,而且返回NO進入forwardingTargetForSelector:方法

  • 在forwardingTargetForSelector:方法中咱們打印一下方法名字,而後返回nil,這樣能夠進入methodSignatureForSelector:方法

  • 在methodSignatureForSelector:方法中,咱們依舊判斷是不是test,若是是,咱們打印下方法名字以及返回一個正確的方法簽名,關於這個方法的傳參能夠看個人上一篇文章,傳送門在底下

  • 最後實現forwardInvocation:方法

咱們看一下打印結果
2018-12-13 12:43:07.287974+0800 RuntimeDemo[41443:8663918] resolveInstanceMethod
2018-12-13 12:43:07.288012+0800 RuntimeDemo[41443:8663918] forwardingTargetForSelector
2018-12-13 12:43:07.288019+0800 RuntimeDemo[41443:8663918] methodSignatureForSelector
2018-12-13 12:43:07.288036+0800 RuntimeDemo[41443:8663918] forwardInvocation
複製代碼

動態添加方法

話很少說先上代碼
void testImp (void) {
    
    NSLog(@"test invoke");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    //若是調用的使咱們的test方法
    if (sel == @selector(test)) {
        NSLog(@"resolveInstanceMethod");
        
        //動態添加test方法實現
        class_addMethod(self, @selector(test), testImp, "v@:");
        
        return YES;
    }else {
        return [super resolveInstanceMethod:sel];
    }
}
複製代碼
  • 首先咱們動態添加方法的時候須要在resolveInstanceMethod:方法中進行實現

  • 調用 class_addMethod方法,其中第一個參數是添加方法的類,這裏咱們寫當前類,第二個參數是添加方法實現的方法選擇器,第三個是添加方法實現的IMP,我在上面實現了這個方法,打印了一句話,最後一個是方法字符指針,關於這個的解釋能夠看個人上一篇文章,傳送門在底下

而後咱們運行一下,打印結果以下
2018-12-13 13:06:33.377111+0800 RuntimeDemo[41479:8670877] resolveInstanceMethod
2018-12-13 13:06:33.377156+0800 RuntimeDemo[41479:8670877] test invoke
複製代碼
  • 說明咱們成功添加了方法實現,這裏咱們已經添加方法實現,因此須要return YES,同時也就不會走到下面的方方法了

動態方法解析

@dynamic

  • 首先咱們看一下@dynamic這個關鍵字

  • 當咱們聲明的屬性在實現當中把它標記爲@dynamic時,至關於它的set方法和get方法是在運行時添加,而不是在編譯時去給它聲明好它具體的實現

  • 動態運行時語言是將函數決議推遲到運行時,實際上就是在運行時爲方法添加運行函數

  • 編譯時語言是在編譯期進行函數決議

傳送門

iOS探索:Runtime之基本數據結構

Github

Demo

相關文章
相關標籤/搜索