iOS的OC的方法的決議與消息轉發原理

前言

筆者整理了一系列有關OC的底層文章,但願能夠幫助到你。html

1.iOS的OC對象建立的alloc原理c++

2.iOS的OC對象的內存對齊緩存

3.iOS的OC的isa的底層原理bash

4.iOS的OC源碼分析之類的結構分析app

5.iOS的OC的方法緩存的源碼分析ide

6.iOS的OC的方法的查找原理函數

OC的方法的查找是經過消息的發送來查找函數的IMP,首先經過objc_msgSend來進行慢速查找(cache_t),若是慢速找不到,就須要進行方法的快速查找,具體能夠了解iOS的OC的方法的查找原理這篇文章。可是,若是經過慢速和快速的查找都找不到的話,就會直接報錯。是否是說若是找不到就沒有辦法走其餘的操做了呢?並非的,接下來這邊文章就是介紹方法的決議和消息轉發原理。爲了接下來的內容介紹定義一個TestObject類。源碼分析

1.方法的決議

實現下面的代碼,其中testErrorMthod方法是沒有在TestObject類聲明和實現的,直接運行是報錯的。post

TestObject *testObject = [[TestObject alloc] init];
[testObject performSelector:@selector(testErrorMthod)];

==========運行結果=================

LGTest[1639:40195] -[TestObject testErrorMthod]: unrecognized selector sent to instance 0x1010c3600
LGTest[1639:40195] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TestObject testErrorMthod]: unrecognized selector sent to instance 0x1010c3600'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff3c7438ab __exceptionPreprocess + 250
	1   libobjc.A.dylib                     0x000000010038dfea objc_exception_throw + 42
	2   CoreFoundation                      0x00007fff3c7c2b61 -[NSObject(NSObject) __retain_OA] + 0
	3   CoreFoundation                      0x00007fff3c6a7adf ___forwarding___ + 1427
	4   CoreFoundation                      0x00007fff3c6a74b8 _CF_forwarding_prep_0 + 120
	5   libobjc.A.dylib                     0x00000001003ccf26 -[NSObject performSelector:] + 70
	6   LGTest                              0x0000000100001afd main + 93
	7   libdyld.dylib                       0x00007fff73d6b7fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
複製代碼

在以前介紹方法的快速查找流程中的lookUpImpOrForward函數中,有這段源碼,在方法查找不到的時候,會執行到裏面去。ui

// No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }
複製代碼

1.1 _class_resolveMethod

下面是_class_resolveMethod的源碼,其中cls是類,sel方法的編號,inst是實例對象。

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);
        }
    }
}
複製代碼

這段源碼對傳進來的cls判斷是不是元類,經過以前的文章能夠知道類方法是存在元類中的。因此若是傳進來的cls是類,就直接執行_class_resolveInstanceMethod函數,若是是元類執行_class_resolveClassMethod函數。

1.2 _class_resolveInstanceMethod

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

複製代碼

其中lookUpImpOrNil函數對類方法SEL_resolveInstanceMethod查找是否有存在,發現SEL_resolveInstanceMethod的實現是resolveInstanceMethod方法,這個是在NSObject類裏面有實現的,因此這個是返回true的。

而後接着執行,下面這段代碼經過 objc_msgSend查找當前的類是否執行 resolveInstanceMethod方法。

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

// Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); 複製代碼

經過前面的文章,能夠知道若是當前的類沒有實現resolveInstanceMethod類方法就會查找父類是否實現,由於在NSObject類中是默認實現返回NO的,若是當前的類有實現resolveInstanceMethod類方法就會執行方法裏的內容。而且在實現的resolveInstanceMethod類方法中對沒有找到的sel從新賦值一個IMP,下面是在TestObject類的實現代碼。

-(void)testOk{
    NSLog(@"%p===testOk",__func__);
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"===執行resolveInstanceMethod===%s===%@",__func__,NSStringFromSelector(sel));
    if(sel == @selector(testErrorMthod)){
        Method okMethod = class_getInstanceMethod(self, @selector(testOk));
        IMP okImp = method_getImplementation(okMethod);
        const char *type =  method_getTypeEncoding(okMethod);
        return class_addMethod(self, sel, okImp, type);
    }
    return [super resolveInstanceMethod:sel];
}


======執行的結果=======
LGTest[2769:92089] ===執行resolveInstanceMethod===+[TestObject resolveInstanceMethod:]===testErrorMthod
LGTest[2769:92089] 0x100001f2a===testOk
Program ended with exit code: 0
複製代碼

此時還執行一次lookUpImpOrNil函數在緩存中查找。因此在TestObject類中定義了這個方法而且爲testErrorMthod賦值了新的IMP

// Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); 複製代碼

上面的是對實例方法的動態決議,其實對類方法的也是差很少的,若是是類方法此時會執行以下源碼

_class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }

複製代碼

1.3 _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*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
複製代碼

經過源碼能夠知道是與_class_resolveInstanceMethod源碼是差很少的,主要區別是,這個源碼的SEL_resolveClassMethod是要實現resolveClassMethod這個類方法,須要在TestObject類中實現類方法resolveClassMethod並在這個方法裏面實現邏輯。可是爲何執行完_class_resolveClassMethod函數以後還會再作一次lookUpImpOrNil函數的判斷呢?由於若是在_class_resolveClassMethod是沒有作處理的,因爲元類的查找方法查找流程是會往根元類查找最終會找到NSObject這個類,因此若是在根元類都找不到的狀況下會找到NSObject類的方法裏面。而NSObject類是有默認實現了這兩個類方法的而且默認返回NO

2. 消息轉發

若是對不存在的方法的查找,沒有實現上面的方法決議,此時會在lookUpImpOrForward函數中執行

// No implementation found, and method resolver didn't help. // Use forwarding. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); 複製代碼

其中_objc_msgForward_impcache就會執行到彙編中的內容,而後執行__objc_msgForward,最終會執行到_objc_forward_handler函數中,最終是會報錯的。

STATIC_ENTRY __objc_msgForward_impcache

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17
	
	END_ENTRY __objc_msgForward
	
// Default forward handler halts the process.
__attribute__((noreturn)) void 
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
複製代碼

這些流程就是在以前文章的方法的查找原理有介紹。是否是就是說消息的轉發流程就跟宗不了呢?並非的。在lookUpImpOrForward函數中有一個能夠打印log的方法log_and_fill_cache中的logMessageSend方法裏面有介紹能夠根據objcMsgLogEnabled屬性來控制打印log

void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);

    objcMsgLogEnabled = enable;
}
複製代碼

爲了能夠看到消息轉發的過程當中實現了那些方法,能夠在mac的項目中實現

extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        TestObject *test = [TestObject alloc] ;
        instrumentObjcMessageSends(true);
        [test performSelector:@selector(testErrorMthod)];
        instrumentObjcMessageSends(false);

    }
    return 0;
}
複製代碼

就能夠在路徑:/tmp/msgSends-找到生成的msgSends文件

這個 msgSends-7265文件的內容

這個打印log是要在mac的項目下才能夠,若是在其餘的項目下是會報objc[6984]: lock 0x100cbf0c0 (runtimeLock) acquired before 0x100cbf040 (objcMsgLogLock) with no defined lock order這種錯誤。

從打印出來的方法能夠知道,在消息的轉發的過程當中執行的過程是resolveInstanceMethod-->forwardingTargetForSelector-->methodSignatureForSelector-->resolveInstanceMethod-->doesNotRecognizeSelector。因此若是不執行resolveInstanceMethod方法決議,會執行forwardingTargetForSelector方法。

2.1 消息快速轉發

objc的源碼中能夠找到在NSObject.mm文件中有定義和實現forwardingTargetForSelector的實例方法和類方法。

+ (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

- (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}
複製代碼

在官方的文檔中有對forwardingTargetForSelector方法的介紹

大概的意思是, forwardingTargetForSelector主要是返回一個不是自身(若是是self會進入死循壞)的對象去處理 sel這個當前類沒法處理的消息,其餘的狀況能夠調用 super方法。若是處理不了,會轉到效率低下的 forwardInvocation。在效率方面, forwardingTargetForSelector領先 forwardInvocation一個數量級,所以,若是能夠的話最好避免使用後者來作消息轉發。下面在 TestObject類中添加多一個 TestForwardObject類,而且在 TestObject類中實現 forwardingTargetForSelector方法。

@interface TestForwardObject : NSObject
@end

@implementation TestForwardObject

-(void)testErrorMthod{
    NSLog(@"TestForwardObject的testErrorMthod方法%p",__func__);
}
@end

//在TestObject類中實現的方法
-(id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"方法名字:%@",NSStringFromSelector(aSelector));
    if(aSelector == @selector(testErrorMthod)){
        return [TestForwardObject alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

//=========運行的結果========
LGTest[1308:29082] 方法名字:testErrorMthod
LGTest[1308:29082] TestForwardObject的testErrorMthod方法0x100001eb4

複製代碼

從中能夠看到消息的轉發到TestForwardObjecttestErrorMthod方法執行了。可是須要注意的是轉發到其餘的類執行的方法必需要和被調用的方法相同方法簽名的方法(方法名、參數列表、返回值類型都必須一致)。不然的話,仍是報錯的。

2.2消息慢速轉發

若是在消息轉發的慢速流程中不作處理,此時會執行到消息轉發的慢速流程中,須要分別執行兩個方法分別是methodSignatureForSelectorforwardInvocation

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"方法名字:%@",NSStringFromSelector(aSelector));
    if(aSelector == @selector(testErrorMthod)){
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"執行forwardInvocation:%s",__func__);
}

//======運行結果==========
LGTest[1222:21266] 方法名字:testErrorMthod
LGTest[1222:21266] 執行forwardInvocation:-[TestObject forwardInvocation:]
複製代碼

在這個流程中methodSignatureForSelector是返回的方法的簽名,能夠參考 蘋果官方類型編碼。能夠發如今forwardInvocation方法中就算不作處理也不會奔潰,由於每一個方法其實就是一個事務,不作處理就會失效,在forwardInvocation中作處理的話,能夠以下:

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"執行forwardInvocation:%s",__func__);
    SEL inVocationSeletor = [anInvocation selector];
    if([[TestForwardObject alloc] respondsToSelector:inVocationSeletor]){
        [anInvocation invokeWithTarget:[TestForwardObject alloc]];
    }else{
        [super forwardInvocation:anInvocation];
    }
}

//=====運行結果===========
LGTest[1465:30634] 方法名字:testErrorMthod
LGTest[1465:30634] 執行forwardInvocation:-[TestObject forwardInvocation:]
LGTest[1465:30634] TestForwardObject的testErrorMthod方法-[TestForwardObject testErrorMthod]
複製代碼

3.最後

OC方法調用是經過objc_msgSend先經過cache_t的快速查找,若是找不到就要進行慢速查找。若是都查找不到方法,就會進入方法的決議消息轉發流程。若是查找的類有實現resolveInstanceMethodresolveClassMethod方法對須要查找的方法作處理就完成,不然就進入消息轉發流程。消息轉發的流程中先進入消息快速轉發流程,須要實現forwardingTargetForSelector方法。不然進入消息慢速轉發流程,須要實現methodSignatureForSelectorforwardInvocation方法。若是都沒有,此時程序只能報錯了。最後附上消息轉發的流程圖

相關文章
相關標籤/搜索