OC消息機制和super關鍵字

博客連接OC消息機制和super關鍵字緩存

消息發送

在Objective-C裏面調用一個方法[object method],運行時會將它翻譯成objc_msgSend(id self, SEL op, ...)的形式。bash

objc_msgSend

objc_msgSend的實如今objc-msg-arm.sobjc-msg-arm64.s等文件中,是經過彙編實現的。這裏主要看在arm64objc-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
複製代碼

上面的流程多是這樣的: 測試

objc_msgsend

CacheLookup的註釋有兩處:ui

  1. calls imp or objc_msgSend_uncached
  2. Locate the implementation for a selector in a class method cache.

即便看不懂彙編代碼,可是從上面的註釋咱們能夠猜想,消息機制會先從緩存中去查找。spa

__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
	br	x17

	END_ENTRY __objc_msgSend_uncached
複製代碼

這裏的MethodTableLookup裏涉及到objc-runtime-new.mm文件中的_class_lookupMethodAndLoadCache3。該函數會調用lookUpImpOrForward函數。debug

lookUpImpOrForward

lookUpImpOrForward會返回一個imp,它的函數實現比較長,可是註釋寫的很是清楚。它的實現主要由如下幾步(這裏直接從緩存獲取開始):code

  1. 經過cache_getImp從緩存中獲取方法,有則返回,不然進入第2步;
  2. 經過getMethodNoSuper_nolock從類的方法列表中獲取,有加入緩存中並返回,不然進入第3步;
  3. 經過父類的緩存和父類的方法列表中尋找是否有對應的imp,此時會進入一個for循環,沿着類的父類一直往上找,直接找到NSObject爲止。若是找到返回,不然進入第4步;
  4. 進入方法決議(method resolve)的過程即調用_class_resolveMethod,若是失敗,進入第5步;
  5. 在緩存、當前類、父類以及方法決議都沒有找到的狀況下,Objective-C還爲咱們提供了最後一次翻身的機會,調用_objc_msgForward_impcache進行方法轉發,若是找到便加入緩存;若是沒有就crash。

上述過程當中有幾個比較重要的函數:cdn

_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

	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_msgForwardobjc中並無其相關實現,只能看到_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:處打個斷點,查看一下調用棧:

message_redirection

_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];
複製代碼

method_signature

代碼執行結果和消息重定向測試的運行結果一致。_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]的時候,調用了那些方法

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
複製代碼
相關文章
相關標籤/搜索