OC訊息機制和super關鍵字
在Objective-C裡面呼叫一個方法 [object method]
,執行時會將它翻譯成 objc_msgSend(id self, SEL op, ...)
的形式。
objc_msgSend
objc_msgSend
的實現在 objc-msg-arm.s
、 objc-msg-arm64.s
等檔案中,是通過彙編實現的。這裡主要看在 arm64
即 objc-msg-arm64.s
的實現。由於彙編不熟,裡面的實現只能連看帶猜。
ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame MESSENGER_START cmpx0, #0// nil check and tagged pointer check b.leLNilOrTagged//(MSB tagged pointer looks negative) ldrx13, [x0]// x13 = isa andx16, x13, #ISA_MASK// x16 = class LGetIsaDone: CacheLookup NORMAL// calls imp or objc_msgSend_uncached LNilOrTagged: /* nil check,如果為空就是呼叫LReturnZero,LReturnZero裡呼叫MESSENGER_END_NIL*/ b.eqLReturnZero// nil check // tagged movx10, #0xf000000000000000 cmpx0, x10 b.hsLExtTag adrpx10, _objc_debug_taggedpointer_classes@PAGE addx10, x10, _objc_debug_taggedpointer_classes@PAGEOFF ubfxx11, x0, #60, #4 ldrx16, [x10, x11, LSL #3] bLGetIsaDone LExtTag: // ext tagged adrpx10, _objc_debug_taggedpointer_ext_classes@PAGE addx10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF ubfxx11, x0, #52, #8 ldrx16, [x10, x11, LSL #3] bLGetIsaDone LReturnZero: // x0 is already zero movx1, #0 movid0, #0 movid1, #0 movid2, #0 movid3, #0 MESSENGER_END_NIL ret END_ENTRY _objc_msgSend 複製程式碼
上面的流程可能是這樣的:

從 CacheLookup
的註釋有兩處:
calls imp or objc_msgSend_uncached Locate the implementation for a selector in a class method cache.
即使看不懂彙編程式碼,但是從上面的註釋我們可以猜測,訊息機制會先從快取中去查詢。
__objc_msgSend_uncached
通過方法名我們可以知道,沒有快取的時候應該會執行 __objc_msgSend_uncached
。
STATIC_ENTRY __objc_msgSend_uncached UNWIND __objc_msgSend_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band x16 is the class to search MethodTableLookup brx17 END_ENTRY __objc_msgSend_uncached 複製程式碼
這裡的 MethodTableLookup
裡涉及到 objc-runtime-new.mm
檔案中的 _class_lookupMethodAndLoadCache3
。該函式會呼叫 lookUpImpOrForward
函式。
lookUpImpOrForward
lookUpImpOrForward
會返回一個 imp
,它的函式實現比較長,但是註釋寫的非常清楚。它的實現主要由以下幾步(這裡直接從快取獲取開始):
cache_getImp getMethodNoSuper_nolock for _class_resolveMethod _objc_msgForward_impcache
上述過程中有幾個比較重要的函式:
_class_resolveMethod
void _class_resolveMethod(Class cls, SEL sel, id inst) { if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] _class_resolveInstanceMethod(cls, sel, inst); } else { // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] _class_resolveClassMethod(cls, sel, inst); if (!lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); } } } 複製程式碼
上述函式會根據當前傳入的類的是不是一個元類,在 _class_resolveInstanceMethod
和 _class_resolveClassMethod
中選擇一個進行呼叫。註釋也說明了這兩個方法的作用就是判斷當前類是否實現了 resolveInstanceMethod:
或者 resolveClassMethod:
方法,然後用 objc_msgSend
執行上述方法。
_class_resolveClassMethod
_class_resolveClassMethod
和 _class_resolveInstanceMethod
實現類似,這裡就只看 _class_resolveClassMethod
的實現。
static void _class_resolveClassMethod(Class cls, SEL sel, id inst) { assert(cls->isMetaClass()); if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { //沒有找到resolveClassMethod方法,直接返回。 return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(_class_getNonMetaClass(cls, inst), SEL_resolveClassMethod, sel); // 快取結果 IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); // 以下程式碼省略不影響閱讀 } 複製程式碼
_objc_msgForward_impcache
STATIC_ENTRY __objc_msgForward_impcache MESSENGER_START nop MESSENGER_END_SLOW // No stret specialization. b__objc_msgForward END_ENTRY __objc_msgForward_impcache ENTRY __objc_msgForward adrpx17, __objc_forward_handler@PAGE ldrx17, [x17, __objc_forward_handler@PAGEOFF] brx17 END_ENTRY __objc_msgForward 複製程式碼
_objc_msgForward_impcache
用來進行訊息轉發,但是其真正的核心是呼叫 _objc_msgForward
。
訊息轉發
關於 _objc_msgForward
在 objc
中並沒有其相關實現,只能看到 _objc_forward_handler
。其實 _objc_msgForward
的實現是在 CFRuntime.c
中的,但是開源出來的 CFRuntime.c
並沒有相關實現,但是也不影響我們對真理的追求。
我們做幾個實驗來驗證訊息轉發。
訊息重定向測試
// .h檔案 @interface AObject : NSObject - (void)sendMessage; @end // .m檔案 @implementation AObject /** 驗證訊息重定向 */ - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(sendMessage)) { return [BObject new]; } return [super forwardingTargetForSelector:aSelector]; } @end // .h檔案 @interface BObject : NSObject - (void)sendMessage; @end // .m檔案 @implementation BObject - (void)sendMessage { NSLog(@"%@ send message", self.class); } @end // 呼叫 AObject *a = [AObject new]; [a sendMessage]; 複製程式碼
執行結果:
2019-03-12 10:18:54.252949+0800 iOSCodeLearning[18165:5967575] BObject send message 複製程式碼
在 forwardingTargetForSelector:
處打個斷點,檢視一下呼叫棧:

_CF_forwarding_prep_0
和 ___forwarding___
這兩個方法會先被呼叫了,之後呼叫了 forwardingTargetForSelector:
。
方法簽名測試
// .h檔案 @interface AObject : NSObject - (void)sendMessage; @end // .m檔案 @implementation AObject /** 訊息重定向 */ - (id)forwardingTargetForSelector:(SEL)aSelector { return nil; } /** 方法簽名測試 */ - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if (aSelector == @selector(sendMessage)) { return [BObject instanceMethodSignatureForSelector:@selector(sendMessage)]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL selector = [anInvocation selector]; if (selector == @selector(sendMessage)) { [anInvocation invokeWithTarget:[BObject new]]; } else { [super forwardInvocation:anInvocation]; } } @end // .h檔案 @interface BObject : NSObject - (void)sendMessage; @end // .m檔案 @implementation BObject - (void)sendMessage { NSLog(@"%@ send message", self.class); } @end // 呼叫 AObject *a = [AObject new]; [a sendMessage]; 複製程式碼

程式碼執行結果和訊息重定向測試的執行結果一致。 _CF_forwarding_prep_0
和 ___forwarding___
這兩個方法又再次被呼叫了,之後程式碼會先執行 forwardingTargetForSelector:
(訊息重定向),訊息重定向如果失敗後呼叫 methodSignatureForSelector:
和 forwardInvocation:
方法簽名。所以說 ___forwarding___
方法才是訊息轉發的真正實現。
crash測試
// .h檔案 @interface AObject : NSObject - (void)sendMessage; @end // .m檔案 @implementation AObject - (id)forwardingTargetForSelector:(SEL)aSelector { return nil; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { } /** 驗證Crash */ - (void)doesNotRecognizeSelector:(SEL)aSelector { if (aSelector == @selector(sendMessage)) { NSLog(@"%@ doesNotRecognizeSelector", self.class); } } @end // .h檔案 @interface BObject : NSObject - (void)sendMessage; @end // .m檔案 @implementation BObject - (void)sendMessage { NSLog(@"%@ send message", self.class); } @end // 呼叫 AObject *a = [AObject new]; [a sendMessage]; 複製程式碼
程式碼執行結果肯定是crash,結合上面的程式碼我們知道訊息轉發會呼叫 ___forwarding___
這個內部方法。 ___forwarding___
方法呼叫順序是 forwardingTargetForSelector:
-> methodSignatureForSelector:
-> doesNotRecognizeSelector:
我們用一張圖表示整個訊息傳送的過程:

super關鍵字
我們先檢視一下執行 [super init]
的時候,呼叫了那些方法

objc_msgSendSuper2
的宣告在 objc-abi.h
中
// objc_msgSendSuper2() takes the current search class, not its superclass. OBJC_EXPORT id _Nullable objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0); 複製程式碼
objc_super
的定義如下:
struct objc_super { /// Specifies an instance of a class. __unsafe_unretained _Nonnull id receiver; /// Specifies the particular superclass of the instance to message. #if !defined(__cplusplus)&&!__OBJC2__ /* For compatibility with old objc-runtime.h header */ __unsafe_unretained _Nonnull Class class; #else __unsafe_unretained _Nonnull Class super_class; #endif /* super_class is the first class to search */ }; 複製程式碼
從上面的定義我們可以知道 receiver
即訊息的實際接收者, super_class
為指向當前類的父類。
所以該函式實際的操作是:從 objc_super
結構體指向的 super_class
開始查詢,直到會找到NSObject的方法為止。找到後以 receiver
去呼叫。當然整個查詢的過程還是和訊息傳送的流程一樣。
所以我們能理解為什麼下面這段程式碼執行的結果都是 AObject
了吧。雖然使用 [super class]
,但是真正執行方法的物件還是 AObject
。
// 程式碼 @implementation AObject - (instancetype)init { if (self = [super init]) { NSLog(@"%@", [super class]); NSLog(@"%@", [self class]); } return self; } @end // 執行結果 2019-03-12 19:44:46.003313+0800 iOSCodeLearning[34431:7234182] AObject 2019-03-12 19:44:46.003442+0800 iOSCodeLearning[34431:7234182] AObject 複製程式碼