ObjC Runtime簡析-- objc_MsgSend

SoC發表於2019-02-04

在ObjC中,方法的呼叫是通過訊息機制依賴runtime來實現的。使用[]給物件傳送一個訊息,轉化為C++的實現是呼叫了objc_msgSend()函式。

objc_msgSend()函式在runtime原始碼中是通過彙編程式碼實現的。它存在與runtime原始碼的這個位置:

objc_msgSend()的原始碼

通過跟讀原始碼我們發現整個objc_msgSend的呼叫流程是這樣的:

ObjC Runtime簡析-- objc_MsgSend

通過上圖我們發現objc_msgSend的呼叫大致分為三個流程:

  • 訊息傳送
  • 動態方法解析
  • 訊息轉發

訊息傳送

通過上圖我們可以看出,訊息傳送經過了判定訊息接受者是否為nil,然後從快取中查詢方法,如果依然查詢不到會遞迴getMethodNoSuper_nolock查詢父類的方法快取列表和父類的方法列表。 如果整個過程下來都找不到需要呼叫的方法,就會進如動態方法解析階段。

動態方法解析

ObjC Runtime簡析-- objc_MsgSend

當進行方法的呼叫的時候如果找不到方法,會按照呼叫的方法型別去呼叫相應的動態解析方法,如果呼叫的是例項方法則會嘗試呼叫+ (BOOL)resolveInstanceMethod:(SEL)sel方法,如果呼叫的是類方法,則會嘗試呼叫+ (BOOL)resolveClassMethod:(SEL)sel方法。我們可以在動態解析中嘗試為該方法新增一個新的已經實現了的方法,如下所示:

// 類方法
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(test)) {
        Method method = class_getClassMethod(self, @selector(test2));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

+ (void)test2 {
    NSLog(@"%s",__func__);
}

// 例項方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        Method method = class_getInstanceMethod(self, @selector(test2));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)test2 {
    NSLog(@"%s",__func__);
}

複製程式碼

訊息轉發

如果訊息機制進行到動態方法解析的時候依然找不到需要呼叫的方法,那麼就會進入訊息轉發階段。 訊息轉發階段會視呼叫的方法的型別呼叫轉發方法,例項方法呼叫- (id)forwardingTargetForSelector:(SEL)aSelector,類方法則呼叫+ (id)forwardingTargetForSelector:(SEL)aSelector方法。該方法要求返回一個可以接受這個訊息的物件。 比如Person呼叫test方法,而Person沒有實現test方法,而Student類實現了這個方法。那麼就可以將這個訊息轉發給Student去處理:

// 例項方法
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        return [[Student alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

// 類方法
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
        if (aSelector == @selector(test)) {
            return [[Student alloc] init];
        }
    return [super forwardingTargetForSelector:aSelector];
}

複製程式碼

返回的Student物件,實際上就相當於呼叫了objc_msgSend([Student new], aSelector)

如果上述方法沒有實現或者說沒有返回一個可以處理訊息的物件,那麼就會進入方法簽名,然後forwardInvocation階段。

這個階段也會視訊息型別呼叫不同的例項方法和類方法: 例項方法:- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector- (void)forwardInvocation:(NSInvocation *)anInvocation;類方法:+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector+ (void)forwardInvocation:(NSInvocation *)anInvocation

methodSignatureForSelector方法返回一個方法簽名,方法簽名就是按照編碼規則生成的一個字串的簽名例項。編碼規則在Runtime簡析中已經介紹過,其實就是用method_t結構中的types初始化的例項,它代表了方法的結構,比如返回值型別,引數型別等。

經過methodSignatureForSelector簽名之後就可以Invocation,提供一個可以呼叫該方法的實施作為target,返回。

// 例項方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
   if (aSelector == @selector(test)) {
       return [NSMethodSignature signatureWithObjCTypes:"v@:"];
   }
   return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
   [anInvocation invokeWithTarget:[[Student alloc] init]];
}

// 類方法
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
   if (aSelector == @selector(test)) {
       return [NSMethodSignature signatureWithObjCTypes:"v@:"];
   }
   return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
   [anInvocation invokeWithTarget:[[Student alloc] init]];
}
複製程式碼

END

ObJC中的方法呼叫的本質是訊息機制,通過objc_msgSend,經過三個階段:方法查詢,動態解析,和訊息轉發階段,如果這幾個階段都沒有提供解決方案,那麼就會丟擲經典的unrecognized selector sent to class錯誤。

相關文章