Objective-C基礎之六(Runtime之深刻理解objc_msgSend)

objc_msgSend源碼解析

源碼查找路徑

在OC中,全部的方法調用底層都會轉換成objc_msgSend方法進行調用,那麼objc_msgSend底層是如何實現的呢?如今咱們就經過objc源碼來了解objc_msgSend的調用流程。c++

objc源碼中查找objc_msgSend方法。發現方法的實如今objc-msg-arm64.sobjc-msg-i386.sobjc-msg開頭的文件中都存在,所以咱們主要查看objc-msg-arm64.s的實現。數組

objc-msg-arm64.sobjc_msgSend是使用匯編來實現的,部分彙編代碼以下緩存

//進入 _objc_msgSend 函數
	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
    //檢查objc_msgSend第一個參數是否爲nil
	cmp	p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    //若是支持tagged pointer,若是p0<=0,執行LNilOrTagged函數
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
	b.eq	LReturnZero
#endif
	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13		// p16 = class
LGetIsaDone:
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    //判斷objc_msgSend第一個參數是否爲nil
	b.eq	LReturnZero		// nil check

	// tagged
	......
	cmp	x10, x16
	b.ne	LGetIsaDone

	// ext tagged
	......
	b	LGetIsaDone
#endif

LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	ret

	END_ENTRY _objc_msgSend
複製代碼

首先會對objc_msgSend的第一個參數,也就是當前接收消息的對象進行nil判斷,而後根據比較結果,跳轉到LNilOrTaggedLNilOrTagged中會調用LGetIsaDone,在LGetIsaDone中則經過CacheLookup來調用方法的實現。bash

.macro CacheLookup
	// p1 = SEL, p16 = isa(p1存放着SEL,p16存放這當前消息接收者的isa指針)
	ldp	p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
	and	w11, w11, 0xffff	// p11 = mask
#endif
	and	w12, w1, w11		// x12 = _cmd & mask
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
		             // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: p12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f
	ldp	p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
	b	1b			// loop

3:	// wrap: p12 = first bucket, w11 = mask
	add	p12, p12, w11, UXTW #(1+PTRSHIFT)
		                        // p12 = buckets + (mask << 1+PTRSHIFT)

	// Clone scanning loop to miss instead of hang when cache is corrupt.
	// The slow path may detect any corruption and halt later.

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: p12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f
	ldp	p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
	b	1b			// loop

3:	// double wrap
	JumpMiss $0
	
.endmacro
複製代碼

CacheLookup其實至關於OC中的宏,CacheLookup的功能其實就是去當前類的方法緩存中去查找方法,若是在緩存中找到方法,會調用CacheHit,若是沒有找到方法,則會調用CheckMiss,繼續查看CheckMiss的定義app

.macro CheckMiss
	// miss if bucket->sel == 0
.if $0 == GETIMP
	cbz	p9, LGetImpMiss
.elseif $0 == NORMAL
	cbz	p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
	cbz	p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
複製代碼

發如今CheckMiss調用了__objc_msgSend_uncached函數

.endmacro

	STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	MethodTableLookup
	TailCallFunctionPointer x17

	END_ENTRY __objc_msgSend_uncached
複製代碼

__objc_msgSend_uncached中,調用了MethodTableLookupoop

.macro MethodTableLookup
	......
	// receiver and selector already in x0 and x1
	mov	x2, x16
	bl	__class_lookupMethodAndLoadCache3
	......
複製代碼

MethodTableLookup中調用的__class_lookupMethodAndLoadCache3在彙編代碼中查找不到對應的實現,所以咱們在整個objc源碼中查詢class_lookupMethodAndLoadCache3的實現,最終找到C語言實現以下學習

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼

objc-runtime-new.mm中的lookUpImpOrForward函數就是咱們最終要找到的核心方法。ui

核心方法解析

lookUpImpOrForward函數源碼以下:spa

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    //是否進行了方法解析操做
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // 若是cache爲true,則會再次到當前類的緩存中查找方法
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
    runtimeLock.lock();
    checkIsKnownClass(cls);
    //判斷當前類有沒有被實現(也就是類中的data()返回的是不是class_rw_t類型)
    if (!cls->isRealized()) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }
    //判斷當前類是否初始化,若是沒有初始化,會向類發送+initialize消息
    if (initialize && !cls->isInitialized()) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

 retry:    
    runtimeLock.assertLocked();

    //再次到當前類的緩存列表中查找方法,若是找到,直接跳轉到done執行,由於可能會在運行時動態給類增長方法
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    {
        //到當前類的方法列表中查詢
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            //若是找到方法,則將方法保存到當前類的緩存中去,而後跳轉到done執行
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    {
        unsigned attempts = unreasonableClassCount();
        //到父類中查找對應的方法實現
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // 先到父類中的緩存中查找
            imp = cache_getImp(curClass, sel);
            if (imp) {
                //判斷imp是否就是函數指針_objc_msgForward_impcache
                if (imp != (IMP)_objc_msgForward_impcache) {
                    //imp不是_objc_msgForward_impcache,將此方法保存到當前類的緩存中去,跳轉到done繼續執行
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                //若是查找到的imp就是_objc_msgForward_impcache函數,那麼直接跳出循環
                    break;
                }
            }
            //在父類的方法列表中查找,若是找到,則先將方法緩存到當前類的方法緩存中,而後返回此方法的地址
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    //若是在當前類和父類中都沒有找到此方法,那麼,就進入動態方法解析階段
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        
        //triedResolver代表當前動態方法解析只會執行一次
        triedResolver = YES;
        goto retry;
    }

    //若是當前類和父類都未實現該方法,而且沒有實現動態解析,那麼就會進入消息轉發階段
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}
複製代碼
  1. 首先會去當前currentClass的緩存中查找方法,若是找到,直接返回IMP,方法結束。若是沒有找到則執行第2步

以前在彙編代碼中就已經查找過緩存了,此處再查找一次緩存是爲了防止運行過程當中動態給currentClass增長方法。

  1. 到currentClass的方法列表中查詢此方法,若是找到方法,則將方法存放到currentClass的cache中去,而且返回IMP,方法結束。若是沒找到,則執行第3步。
  2. 根據superClass,經過繼承結構到currentClass的父類的方法緩存中查找,若是在緩存中找到方法,則執行第4步。若是在superClass的緩存中沒有找到方法,則執行第5步
  3. 判斷此方法是不是_objc_msgForward_impcache方法,若是不是,則將此方法存放到currentClass的cache中,而且返IMP,方法結束。若是此方法是_objc_msgForward_impcache,直接執行第6步

_objc_msgForward_impcache實際上是一個存放在內存中的函數指針,爲彙編實現,內部會調用__objc_msgForward函數。

  1. 到superClass的方法列表中查詢方法,若是找到方法,則將此方法保存到currentClass的方法緩存中,而後返回方法地址。若是沒找到,則執行第6步
  2. 判斷當前是否執行執行過方法解析,若是執行過,則進入第7步。若是沒有執行過,進入動態方法解析階段,而且設置triedResolver=YES,而後重複執行第1步。
  3. 進入消息轉發階段,將_objc_msgForward_impcache函數指針緩存到currentClass的cache中,而且返回_objc_msgForward_impcache的imp。

具體流程圖以下:

所以咱們能夠得出結論,objc_msgSend分爲3個步驟

  1. 消息發送(方法查找)
  2. 動態方法解析
  3. 消息轉發

方法查找補充

在上文中lookUpImpOrForward函數用來進行方法查找,在函數中使用cache_getImp函數去類對象的緩存中查找方法。使用getMethodNoSuper_nolock函數去類對象的方法列表中去查詢,getMethodNoSuper_nolock源碼以下:

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{   
    //遍歷方法列表,獲得method_list_t,經過search_method_list函數去method_list_t中查找對應方法
    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}
複製代碼

由於類對象中的class_rw_t中方法列表是method_array_t類型的二維數組,內部存放着method_list_t,所以,getMethodNoSuper_nolock函數就是遍歷二維數組,而後拿到對應的method_list_t,最後調用search_method_list函數去method_list_t中查找對應的方法,源碼以下:

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    //判斷當前method_list_t是否排好序
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        //若是排好序,則使用二分查找,提升效率
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // 若是沒有排序,則遍歷查詢
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }

    return nil;
}
複製代碼

search_method_list函數中會對method_list_t是否排好序進行判斷,若是method_list_t未排序,則經過遍歷去查詢方法列表。若是已排序,則會調用findMethodInSortedMethodList進行二分查找,提升查找效率,源碼以下:

static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    //count >>= 1表示count右移一位,而後賦值給count。至關於count = count / 2
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}
複製代碼

findMethodInSortedMethodList函數就是典型的二分查找。有興趣的同窗能夠自行去學習有關二分查找的更多知識。

objc_msgSend執行流程

在本文中,全部有關objc_msgSend的僞碼,可參考Hmmm, What's that Selector?

消息發送

在OC中方法調用底層是經過調用objc_msgSend函數來執行方法。

[anObject doThings:things];

// 編譯以後
objc_msgSend(anObject, @selector(doThings:), things);
複製代碼

objc_msgSend的僞碼實現以下

id objc_msgSend(id self, SEL _cmd, ...) {
  //經過self拿到對應的類對象
  Class class = object_getClass(self);
  //去類對象中查找對應的方法實現
  IMP imp = class_getMethodImplementation(class, _cmd);
  return imp ? imp(self, _cmd, ...) : 0;
}
複製代碼

其中class_getMethodImplementation函數就是方法查找的過程,具體的方法查找過程可參考上文,或者參考以前的文章Objective-C基礎之五(Runtime之Class結構解析)

動態方法解析

源碼解析

若是在當前類和其父類中都沒有實現方法,那麼會進行動態方法解析,也就是上文中的resolveMethod函數,在此函數中能夠動態爲類增長方法。

//若是在當前類和父類中都沒有找到此方法,那麼,就進入動態方法解析階段
if (resolver  &&  !triedResolver) {
    runtimeLock.unlock();
    resolveMethod(cls, sel, inst);
    runtimeLock.lock();
        
    //triedResolver代表當前動態方法解析只會執行一次
    triedResolver = YES;
    goto retry;
}
複製代碼

每次方法調用都會判斷是否進行過動態方法解析,若是沒有進行過動態方法解析,則會調用resolveMethod函數以下

static void resolveMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());
    
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            resolveInstanceMethod(cls, sel, inst);
        }
    }
}
複製代碼

若是當前類不是元類,那麼直接調用resolveInstanceMethod函數,在函數內部會查找類中是否實現了resolveInstanceMethod方法,若是實現了,則會調用resolveInstanceMethod方法。resolveInstanceMethod源碼以下

static void resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());
    //lookUpImpOrNil內部其實就是調用lookUpImpOrForward方法,去當前類及其父類中查找SEL_resolveInstanceMethod
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }
    //經過objc_msgSend調用resolveInstanceMethod方法
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    msg(cls, SEL_resolveInstanceMethod, sel);
}
複製代碼

上文中lookUpImpOrNil其實內部就是調用lookUpImpOrForward函數去查找SEL_resolveInstanceMethod方法,若是沒有找到,而且lookUpImpOrForward返回的imp==_objc_msgForward_impcache時,則返回nil。也就是判斷當前類以及它的父類是否實現了SEL_resolveInstanceMethod方法,若是未實現,則不執行下一步的objc_msgSend(cls, SEL_resolveInstanceMethod, sel)方法。

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}
複製代碼

若是當前類或者它的父類實現了SEL_resolveInstanceMethod方法,則調用objc_msgSend(cls, SEL_resolveInstanceMethod, sel)從新走一遍消息發送流程。目的就是找到SEL_resolveInstanceMethod方法而且進行調用。

lookUpImpOrForward只是返回方法的地址,具體方法的調用由彙編實現。

若是當前類是元類,那麼就會調用resolveClassMethod,具體內部實現和類對象的實現一致。不論是resolveInstanceMethod仍是resolveClassMethod,咱們均可以在這兩個方法中動態的爲類和元類增長方法。當執行過動態方法解析以後,會執行如下操做

//triedResolver代表當前動態方法解析只會執行一次
triedResolver = YES;
goto retry;
複製代碼

首先會將triedResolver標記設置爲YES,而後跳轉到retry標記處從新執行一次方法查找流程,若是咱們在resolveInstanceMethod或者resolveClassMethod方法中動態爲類新增了方法,那麼執行retry的時候會再次到類的緩存或者方法列表中查找到對應的方法而且執行。

Demo實現

  • 首先建立XLPersoon類,而後在XLPerson.h中添加如下聲明,可是不添加方法的實現。
@interface XLPerson : NSObject

- (void)testInstance;
+ (void)testClass;

@end
複製代碼
  • 此時,咱們在main函數中調用不論是testInstance方法仍是testClass方法,最終都會報一個很常見的錯誤
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[XLPerson test]: unrecognized selector sent to class 0x1000022d0'
複製代碼
  • 而後咱們在XLPerson.m中添加動態解析的方法,以下
@implementation XLPerson

- (void)personTestInstance{
    NSLog(@"personTestInstance");
}

+ (void)personTestClass{
    NSLog(@"personTestClass");
}
//對象方法消息轉發
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
        Method method = class_getInstanceMethod(self, @selector(personTestInstance));
        class_addMethod(self,
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
//類方法消息轉發
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(test)) {
        Method method = class_getClassMethod(self, @selector(personTestClass));
        class_addMethod(object_getClass(self),
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveClassMethod:sel];
}

@end
複製代碼
  • 再次調用testInstance方法和testClass方法會發現控制檯打印出了以下結果,說明消息轉發過程當中咱們動態爲類和元類增長的方法都被調用了。
2020-01-13 10:10:09.364022+0800 Test[74806:8439331] personTestInstance
2020-01-13 10:10:12.285118+0800 Test[74806:8439331] personTestClass
複製代碼

流程圖

動態方法解析的流程圖以下

消息轉發

若是咱們沒有在消息轉發的時候動態爲類或者元類增長方法,那麼,就會來到最後一步,就是消息轉發階段。消息轉發過程由於是不開源的,因此咱們先從Demo入手來看一下消息轉發的流程。

消息轉發Demo

  • 繼續使用上文中的XLPerson類,類中仍是建立了testInstancetestClass兩個方法。以後從新建立XLTeacher類,類中一樣建立如下方法,而且進行方法實現
@interface XLTeacher : NSObject

- (void)testInstance;
+ (void)testClass;

@end

@implementation XLTeacher

- (void)testInstance{
    NSLog(@"%s", __func__);
}
+ (void)testClass{
    NSLog(@"%s", __func__);
}

@end
複製代碼
  • 消息轉發首先會判斷類是否實現了forwardingTargetForSelector方法,若是實現了forwardingTargetForSelector方法,會拿到forwardingTargetForSelector方法返回的實例對象或者類對象,而後經過objc_msgSend(返回的對象,sel)執行消息發送。在XLPerson.m中增長如下方法
@implementation XLPerson
//實例對象方法的轉發,須要返回一個實例對象
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(testInstance)) {
        return [[XLTeacher alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

//類對象方法的轉發,須要返回一個類對象
+ (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(testClass)) {
        return [XLTeacher class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end
複製代碼

在main函數中執行XLPerson的方法testInstancetestClass,會發現最後執行了XLTeacher中的同名方法testInstancetestClass

  • 若是類未實現forwardingTargetForSelector方法,那麼首先會判斷類是否實現了forwardingTargetForSelector方法,若是實現了forwardingTargetForSelector方法,會拿到forwardingTargetForSelector方法返回的方法簽名。若是方法簽名爲nil,則會執行報錯。在XLPerson中增長以下方法,返回對應方法的方法簽名
@implementation XLPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(testInstance)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(testClass)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

@end
複製代碼
  • 若是forwardingTargetForSelector返回的方法簽名有效,會調用forwardInvocation方法,其實這個方法是將方法調用者方法名方法參數封裝成了NSInvocation對象。而後使用NSInvocation對象來獲取指定調用者的指定方法,而且進行調用。以下
@implementation XLPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(testInstance)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(testClass)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

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

+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[XLTeacher class]];
}

@end
複製代碼
  • 因爲methodSignatureForSelector已經返回了正確的方法簽名,因此在forwardInvocation方法中就不須要額外設置方法簽名了。而且須要保證方法簽名和咱們須要調用的方法簽名保持一致。
  • 而且以上methodSignatureForSelector方法和forwardInvocation方法都有對應的實例方法和類方法。實例方法須要返回一個實例對象,類方法須要返回類對象。
  • 最後在main函數中執行XLPerson的testInstance方法和testClass方法,會發現最終調用了XLTeacher的testInstance方法和testClass方法,至此,整個消息轉發的流程就完成了。

經過僞碼進一步瞭解消息轉發的內部實現

首先上文中說到,若是以上三個階段都沒有找到方法的話,程序會報錯,報錯信息以下:

2020-01-13 11:42:58.563144+0800 Test[89986:8560251] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[XLPerson testInstance]: unrecognized selector sent to instance 0x102856690'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff37632f53 __exceptionPreprocess + 250
	1   libobjc.A.dylib                     0x00007fff6d6f8835 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff376bd106 -[NSObject(NSObject) __retain_OA] + 0
	3   CoreFoundation                      0x00007fff375d96cb ___forwarding___ + 1427
	4   CoreFoundation                      0x00007fff375d90a8 _CF_forwarding_prep_0 + 120
	5   Test                                0x0000000100000d9b main + 91
	6   libdyld.dylib                       0x00007fff6ea5b2e5 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
複製代碼

在報錯堆棧中,明顯能夠看出,執行消息轉發是經過___forwarding___來完成的,可是咱們在源碼中是找不到___forwarding___的實現的,由於對於消息轉發這部分的源碼並不開源,所以咱們能夠藉助上文提到的文章Hmmm, What's that Selector?來研究___forwarding___實現過程。

void __forwarding__(BOOL isStret, void *frameStackPointer, ...) {
  //經過堆棧指針frameStackPointer獲取當前接收者
  id receiver = *(id *)frameStackPointer;
  //經過frameStackPointer+4獲取到方法選擇器
  SEL sel = *(SEL *)(frameStackPointer + 4);
  //獲取到receiver對應的類對象
  Class receiverClass = object_getClass(receiver);
    
  //若是receiverClass實現了forwardingTargetForSelector
  if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
    //調用receiver的forwardingTargetForSelector方法,獲取到須要轉發的target
    id forwardingTarget = [receiver forwardingTargetForSelector:sel];
    if (forwardingTarget) {
      //而後經過調用objc_msgSend函數從新執行消息發送流程,將forwardingTarget做爲第一個參數
      return objc_msgSend(forwardingTarget, sel, ...);
    }
  }
  
  //若是對象已經被釋放,則打印錯誤信息
  const char *className = class_getName(object_getClass(receiver));
  const char *zombiePrefix = "_NSZombie_";
  size_t prefixLen = strlen(zombiePrefix);
  if (strncmp(className, zombiePrefix, prefixLen) == 0) {
    CFLog(kCFLogLevelError,
          @"-[%s %s]: message sent to deallocated instance %p",
          className + prefixLen,
          sel_getName(sel),
          receiver);
    <breakpoint-interrupt>
  }
  //若是receiverClass實現了methodSignatureForSelector方法
  if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
    //調用receiver的methodSignatureForSelector獲取到對應的方法簽名
    NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
    if (methodSignature) {
      //判斷methodSignature是不是結構體,若是不是,則打印錯誤信息
      BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
      if (signatureIsStret != isStret) {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
              sel_getName(sel),
              signatureIsStret ? "" : not,
              isStret ? "" : not);
      }
      //若是receiverClass實現了forwardInvocation方法
      if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
        //根據方法簽名和堆棧指針獲取到對應的invocation
        NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature
                                                                          frame:frameStackPointer];
        //調用receiver的forwardInvocation方法,將剛剛生成的invocation傳遞過去
        [receiver forwardInvocation:invocation];

        void *returnValue = NULL;
        //獲取到invocation中的返回值
        [invocation getReturnValue:&value];
        return returnValue;
      } else {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
              receiver,
              className);
        return 0;
      }
    }
  }

  const char *selName = sel_getName(sel);
  SEL *registeredSel = sel_getUid(selName);
  //若是消息轉發都失敗的話走如下錯誤判斷
  if (sel != registeredSel) {
    //若是當前的sel和sel_getUid(selName)獲取到的sel不一致
    CFLog(kCFLogLevelWarning ,
          @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
          sel,
          selName,
          registeredSel);
  } else if (class_respondsToSelector(receiverClass, @selector(doesNotRecognizeSelector:))) {
    //若是當前receiverClass實現了doesNotRecognizeSelector方法,則調用此方法
    [receiver doesNotRecognizeSelector:sel];
  } else {
    //未實現doesNotRecognizeSelector的方法的日誌
    CFLog(kCFLogLevelWarning ,
          @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
          receiver,
          className);
  }

  // 殺死進程
  kill(getpid(), 9);
}
複製代碼

由上述僞代碼,能夠獲得消息轉發的總體流程

  1. 判斷當前receiverClass是否實現了forwardingTargetForSelector方法,若是實現了,調用forwardingTargetForSelector方法拿到返回的forwardingTarget,而後經過objc_msgSend(forwardingTarget, sel, ...)函數執行消息發送的流程。若是沒有實現,則執行第2步。
  2. 判斷當前receiverClass是否實現了methodSignatureForSelector方法,若是沒有實現,則執行第5步。若是實現了,則執行第3步。
  3. 後判斷當前receiverClass是否實現了forwardInvocation,若是未實現,打印錯誤信息。若是實現了,則執行第4步。
  4. 經過receiverClassmethodSignatureForSelector方法,拿到方法簽名,而後根據方法簽名和堆棧指針生成對應的invocation對象,而後調用receiverClassforwardInvocation方法,將生成的invocation做爲參數傳過去,而且將方法返回值return回去(若是不存在,則返回NULL)。
  5. 若是沒有實現對應的消息轉發方法,則調用receiverClassdoesNotRecognizeSelector方法打印錯誤信息,而且退出當前進程。

消息轉發流程圖以下

objc_msgSend總結

以上就是objc_msgSend的完整流程,大致能夠分爲3個階段,消息發送、動態方法解析和消息轉發

消息發送

  1. 首先會去當前receiverClass的緩存中查找方法,若是找到,調用方法。若是沒有找到則執行第2步
  2. 到receiverClass的方法列表中查詢此方法(也就是到class_rw_t中查找),若是找到方法,則先將方法存放到receiverClass的cache中去,而後調用方法。若是沒找到,則執行第3步。
  3. 根據superClass,經過繼承結構到receiverClass的父類的方法緩存中查找,若是在緩存中找到方法,則執行第4步。若是在superClass的緩存中沒有找到方法,則執行第5步
  4. 判斷此方法是不是_objc_msgForward_impcache方法,若是不是,則將此方法存放到receiverClass的cache中,而且返IMP,方法結束。若是此方法是_objc_msgForward_impcache,直接執行第6步
  5. 到superClass的方法列表中查詢方法,若是找到方法,則將此方法保存到receiverClass的方法緩存中,而後調用該方法。若是沒找到,則執行第6步
  6. 判斷當前是否執行執行過方法解析,若是執行過,則進入第7步。若是沒有執行過,進入動態方法解析階段,而且設置triedResolver=YES,而後重複執行第1步。
  7. 進入消息轉發階段,將_objc_msgForward_impcache函數指針緩存到currentClass的cache中,而且調用_objc_msgForward_impcache

動態方法解析

  1. 若是當前未執行過動態方法解析,那麼會調用當前類的resolveInstanceMethod或者resolveClassMethod
  2. resolveInstanceMethod或者resolveClassMethod中動態爲類增長方法,而且插入到方法列表中
  3. 執行完動態方法解析後,會設置是否進行動態方法解析標誌爲YES,而後從新執行消息發送的整個流程,若是咱們動態爲類增長了方法,那麼會找到此方法並執行。若是未增長方法,則會執行最後的消息轉發。

消息轉發

  1. 判斷當前receiverClass是否實現了forwardingTargetForSelector方法,若是實現了,調用forwardingTargetForSelector方法拿到返回的forwardingTarget,而後經過objc_msgSend(forwardingTarget, sel, ...)函數執行消息發送的流程。若是沒有實現,則執行第2步。
  2. 判斷當前receiverClass是否實現了methodSignatureForSelector方法,若是沒有實現,則執行第5步。若是實現了,則執行第3步。
  3. 判斷當前receiverClass是否實現了forwardInvocation,若是未實現,打印錯誤信息。若是實現了,則執行第4步。
  4. 經過receiverClassmethodSignatureForSelector方法,拿到方法簽名,而後根據方法簽名和堆棧指針生成對應的invocation對象,而後調用receiverClassforwardInvocation方法,將生成的invocation做爲參數傳過去,而且將方法返回值return回去(若是不存在,則返回NULL)。
  5. 若是沒有實現對應的消息轉發方法,則調用receiverClassdoesNotRecognizeSelector方法打印錯誤信息,而且退出當前進程。

消息轉發流程簡圖以下:

結束語

以上內容純屬我的理解,若是有什麼不對的地方歡迎留言指正。

一塊兒學習,一塊兒進步~~~

相關文章
相關標籤/搜索