iOS 消息調用的過程

咱們知道將源代碼轉化爲可執行的文件要通過三個階段:編譯、連接、運行。不一樣的編譯語言有有所不一樣。緩存

在iOS中函數的調用,實質就是給對象發消息。而在程序的運行過程當中,函數調用的實現是不肯定的,只有在運行時纔去肯定函數的實現。在程序運行時,編譯器會把函數的調用轉換成objc_msgsend。這個函數會動態的尋找下一個要執行的方法。bash

  1. 編譯階段: [receiver selector]方法調用被編譯爲:
    1. objc_msgSend(receiver, selector)(不帶參數方法);
    2. objc_msgSend(receiver, selector, org1, org2)(帶參數方法);
  2. 運行時階段:
    1. 經過receiver(消息接受者 )的isa指針,找到receiver所屬的Class(類);
    2. receiver所屬類的method list(方法列表)中找對應的selector(先找方法緩存列表再找方法列表);
    3. 若是在Class中沒有找到selector對應的實現,就繼續去superClass(父類)方法列表中查找;
    4. 若是找到對應selector,直接執行receiverselector方法對應的實現(IMP);
    5. 若找不到對應的selector,消息將被轉發或者臨時想receiver添加這個selector對應的實現,不然會crash。
  • 函數的調用過程圖以下:
    調用.png

尋找方法的實現過程大體以下: ide

方法實現.png

消息轉發

如上圖中在繼承關係中找不到方法實現時,程序就會crash。可是在crash以前咱們還能夠重寫如下四個方法進行處理:函數

///當調用一個不存在的類方法時調用
+ (BOOL)resolveClassMethod:(SEL)sel;

///當調用一個不存在的實例方法時調用
+ (BOOL)resolveInstanceMethod:(SEL)sel;

///將這個不存在的方法重定向到其餘類進行處理,返回一個類的實例
- (id)forwardingTargetForSelector:(SEL)aSelector;

///將這個不存在的方法打包成NSInvocation丟進來,須要調用invokeWithTarget:給某個能執行方法的實例
- (void)forwardInvocation:(NSInvocation *)anInvocation;
複製代碼

以實例方法爲例來講一下這幾個方法的調用流程:ui

  1. 首先調用+ (BOOL)resolveClassMethod:(SEL)sel
  2. 若是返回NO,就調用- (id)forwardingTargetForSelector:(SEL)aSelector
  3. 若是返回nil,就調用- (void)forwardInvocation:(NSInvocation *)anInvocation 具體流程圖以下:
    流程.png
    咱們看一下下面的未實現的點擊方法調用狀況:
[button addTarget:self action:@selector(tapAction) forControlEvents:UIControlEventTouchUpInside];

void addMethod(id self, SEL _cmd) {
    NSLog(@"addMethod complete");
}
///當調用一個不存在的類方法時調用
+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"resolveClassMethod complete");
    return YES;
}

///當調用一個不存在的實例方法時調用
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    class_addMethod(self, sel, (IMP)addMethod, "v@:*");
    NSLog(@"resolveInstanceMethod complete");
    return YES;
}

///將這個不存在的方法重定向到其餘類進行處理,返回一個類的實例
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"forwardingTargetForSelector complete");
    return nil;
}

///將這個不存在的方法打包成NSInvocation丟進來,須要調用invokeWithTarget:給某個能執行方法的實例
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"forwardInvocation complete");
}
複製代碼

打印數據:spa

2019-07-19 16:16:48.429089+0800 ThinTableVIew1[37672:364932] resolveInstanceMethod complete
2019-07-19 16:16:48.429242+0800 ThinTableVIew1[37672:364932] addMethod complete
複製代碼

從上面的打印信息咱們能夠知道tapAction方法調用到resolveInstanceMethod就中止了,由於咱們給系統添加了一個方法,並返回了YES。這時候就不會再調用兩個forward方法了。 簡單說來,這四個方法都是用來添加未處理方法的。區別在於,resolveInstanceMethod是在本類中添加方法,並告訴系統該方法是否執行;forwardingTargetForSelector 是本身處理不了,轉給其它實例作處理;若是通過以上幾步仍是處理不了,那麼就走到了forwardInvocation中,系統會把這個方法的全部信息都打包給咱們,作最後的處理。3d

相關文章
相關標籤/搜索