博客連接OC消息機制和super關鍵字緩存
在Objective-C裏面調用一個方法[object method]
,運行時會將它翻譯成objc_msgSend(id self, SEL op, ...)
的形式。bash
objc_msgSend
的實如今objc-msg-arm.s
、objc-msg-arm64.s
等文件中,是經過彙編實現的。這裏主要看在arm64
即objc-msg-arm64.s
的實現。因爲彙編不熟,裏面的實現只能連看帶猜。函數
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
cmp x0, #0 // nil check and tagged pointer check
b.le LNilOrTagged // (MSB tagged pointer looks negative)
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LNilOrTagged:
/* nil check,若是爲空就是調用LReturnZero,LReturnZero裏調用MESSENGER_END_NIL*/
b.eq LReturnZero // nil check
// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LExtTag
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
LExtTag:
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
MESSENGER_END_NIL
ret
END_ENTRY _objc_msgSend
複製代碼
上面的流程多是這樣的: 測試
從CacheLookup
的註釋有兩處:ui
calls imp or objc_msgSend_uncached
Locate the implementation for a selector in a class method cache.
即便看不懂彙編代碼,可是從上面的註釋咱們能夠猜想,消息機制會先從緩存中去查找。spa
經過方法名咱們能夠知道,沒有緩存的時候應該會執行__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
br x17
END_ENTRY __objc_msgSend_uncached
複製代碼
這裏的MethodTableLookup
裏涉及到objc-runtime-new.mm
文件中的_class_lookupMethodAndLoadCache3
。該函數會調用lookUpImpOrForward
函數。debug
lookUpImpOrForward
會返回一個imp
,它的函數實現比較長,可是註釋寫的很是清楚。它的實現主要由如下幾步(這裏直接從緩存獲取開始):code
cache_getImp
從緩存中獲取方法,有則返回,不然進入第2步;getMethodNoSuper_nolock
從類的方法列表中獲取,有加入緩存中並返回,不然進入第3步;for
循環,沿着類的父類一直往上找,直接找到NSObject爲止。若是找到返回,不然進入第4步;_class_resolveMethod
,若是失敗,進入第5步;_objc_msgForward_impcache
進行方法轉發,若是找到便加入緩存;若是沒有就crash。上述過程當中有幾個比較重要的函數:cdn
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_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*/);
// 如下代碼省略不影響閱讀
}
複製代碼
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
adrp x17, __objc_forward_handler@PAGE
ldr x17, [x17, __objc_forward_handler@PAGEOFF]
br x17
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___
方法纔是消息轉發的真正實現。
// .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 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
複製代碼