系統(tǒng)底層源碼分析(19)——?jiǎng)討B(tài)方法決議&消息轉(zhuǎn)發(fā)

接著上篇文章(系統(tǒng)底層源碼分析(18)——objc_msgSend)繼續(xù)說(shuō):

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    ...
    if (!cls->isRealized()) {
        realizeClass(cls);//準(zhǔn)備-父類(lèi)
    }
    ...
 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache.

    imp = cache_getImp(cls, sel);//從緩存獲取imp
    if (imp) goto done;//找到返回
    //緩存中沒(méi)有找到就去查找方法列表
    // Try this class's method lists.
    {//局部作用域,可避免名字沖突
        Method meth = getMethodNoSuper_nolock(cls, sel);//查找
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);//緩存
            imp = meth->imp;
            goto done;
        }
    }
    //子類(lèi)找不到就遞歸找父類(lèi)
    // Try superclass caches and method lists.
    {
        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) { ... }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);//從父類(lèi)緩存獲取imp
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    break;
                }
            }
            //緩存中沒(méi)有找到就去查找父類(lèi)方法列表
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);//查找
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);//緩存
                imp = meth->imp;
                goto done;
            }
        }
    }
    
    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);//對(duì)找不到的方法進(jìn)行處理
        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;//再次查找
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;//報(bào)錯(cuò)
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}
  • 報(bào)錯(cuò)
  1. 之前說(shuō)到調(diào)用方法昔搂,底層會(huì)調(diào)用objc_msgSend,進(jìn)入快速查找流程,找不到就進(jìn)入慢速查找流程荐虐,然后來(lái)到lookUpImpOrForward泥彤,如果最后還找不到颈抚,就會(huì)調(diào)用_objc_msgForward_impcache報(bào)錯(cuò):
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret

beq __objc_msgForward
b   __objc_msgForward_stret

END_ENTRY __objc_msgForward_impcache


ENTRY __objc_msgForward
// Non-stret version

MI_GET_EXTERN(r12, __objc_forward_handler)
ldr r12, [r12]
bx  r12

END_ENTRY __objc_msgForward

上述是匯編代碼,調(diào)用流程是:

__objc_msgForward_impcache -> __objc_msgForward -> __objc_forward_handler

  1. __objc_forward_handler會(huì)回到源碼中责静,查找時(shí)需要去掉一個(gè)下劃線(xiàn):
__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);//打印的錯(cuò)誤信息
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

打印信息是不是很熟悉,就是我們調(diào)用沒(méi)有實(shí)現(xiàn)的方法時(shí)報(bào)的錯(cuò):

報(bào)錯(cuò)信息
  • 動(dòng)態(tài)方法決議
  1. 如果調(diào)用沒(méi)有實(shí)現(xiàn)的方法就會(huì)報(bào)錯(cuò)盖桥,但是系統(tǒng)給了挽救的機(jī)會(huì)灾螃,就在報(bào)錯(cuò)前進(jìn)行處理的_class_resolveMethod函數(shù):
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);//實(shí)例方法決議
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);//類(lèi)方法決議
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {//根據(jù)isa走位圖,查找實(shí)例方法和類(lèi)方法最終都走向NSObject類(lèi)揩徊,所以再進(jìn)行一次實(shí)例方法決議
            _class_resolveInstanceMethod(cls, sel, inst);//實(shí)例方法決議
        }
    }
}
  1. 我們先來(lái)看對(duì)實(shí)例方法的處理腰鬼,首先會(huì)查找resolveInstanceMethod
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) //查找resolveInstanceMethod并緩存
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;//消息發(fā)送,快速查找
    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*/);//再找一次
    ...
}
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;
}
  1. 找到之后因?yàn)闀?huì)緩存塑荒,所以下次查找時(shí)熄赡,可以通過(guò)objc_msgSend更快查找并調(diào)用,這里要注意找的是系統(tǒng)方法resolveInstanceMethod
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}
  1. 這時(shí)可以通過(guò)在Person類(lèi)中重寫(xiě)resolveInstanceMethod來(lái)進(jìn)行處理(動(dòng)態(tài)添加方法):
//Person類(lèi)中
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(say)) {
        IMP sayHelloIMP = class_getMethodImplementation(self, @selector(sayHello));
        Method sayHelloMethod = class_getInstanceMethod(self, @selector(sayHello));
        const char *sayHelloType = method_getTypeEncoding(sayHelloMethod);
        return class_addMethod(self, sel, sayHelloIMP, sayHelloType);
    }
    return [super resolveInstanceMethod:sel];
}
  1. 返回后齿税,回到第2步彼硫,這時(shí)再次lookUpImpOrNil,由于動(dòng)態(tài)添加了方法凌箕,所以這次可以找到方法拧篮,然后返回后就回到第1步進(jìn)行retry,重新查找并找到牵舱,所以就不會(huì)報(bào)錯(cuò)他托。

  2. 另外類(lèi)方法處理也是差不多:

/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
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并緩存
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;//消息發(fā)送,快速查找
    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*/);
    ...
}
//Person類(lèi)中
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(cls_say)) {
        IMP sayHelloIMP = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(cls_sayHello));
        Method sayHelloMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(cls_sayHello));
        const char *sayHelloType = method_getTypeEncoding(sayHelloMethod);
        return class_addMethod(objc_getMetaClass("Person"), sel, sayHelloIMP, sayHelloType);
    }
    return [super resolveClassMethod:sel];
}
  • 消息轉(zhuǎn)發(fā)

如果動(dòng)態(tài)方法決議也沒(méi)有處理仆葡,就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)流程(簡(jiǎn)單來(lái)說(shuō)就是交給別人處理):

  1. 首先會(huì)進(jìn)入快速轉(zhuǎn)發(fā):
//Person類(lèi)中
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(say)) {
        return [Teacher alloc];//返回對(duì)象
    }
    return [super forwardingTargetForSelector:aSelector];
}
  1. 如果快速轉(zhuǎn)發(fā)也沒(méi)有處理赏参,就進(jìn)入慢速轉(zhuǎn)發(fā)流程:

2-1. methodSignatureForSelector返回SEL方法的簽名,返回的簽名是根據(jù)方法的參數(shù)來(lái)封裝的把篓。這個(gè)函數(shù)讓重載方有機(jī)會(huì)拋出一個(gè)函數(shù)的簽名,用于生成NSInvocation腰涧,再由后面的 forwardInvocation去執(zhí)行:

//Person類(lèi)中
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(say)) { 
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法簽名
    }
    return [super methodSignatureForSelector:aSelector];
}

2-2. 在forwardInvocation里可以將NSInvocation多次轉(zhuǎn)發(fā)到多個(gè)對(duì)象中韧掩,這也是這種方式靈活的地方。(而forwarding TargetForSelectorSelec只能以Selector的形式轉(zhuǎn)向一個(gè)對(duì)象):

//Person類(lèi)中
- (void)forwardInvocation:(NSInvocation *)anInvocation{
   SEL aSelector = [anInvocation selector];
   Teacher *teacher = [Teacher alloc];
   if ([teacher respondsToSelector:aSelector])
       [anInvocation invokeWithTarget:teacher];
   else
       [super forwardInvocation:anInvocation];
}
  • 實(shí)現(xiàn)forwardInvocation后窖铡,就算不處理應(yīng)用也不會(huì)再崩潰疗锐。
沒(méi)有實(shí)現(xiàn)的方法處理流程圖
  • 補(bǔ)充
  1. 為了更充分說(shuō)明這些流程的有效性坊谁,我們可以通過(guò)instrumentObjcMessageSends獲取系統(tǒng)日志:
extern void instrumentObjcMessageSends(BOOL flag);//系統(tǒng)方法

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *student = [Student alloc] ;
        instrumentObjcMessageSends(true);//路徑在/tmp/
        [student say];
        instrumentObjcMessageSends(false);
    }
    return 0;
}
  1. 然后直接前往/tmp(文件夾路徑),然后打開(kāi)msgSends-前綴的文件:
//動(dòng)態(tài)方法決議
+ Student NSObject resolveInstanceMethod:
+ Student NSObject resolveInstanceMethod:
//消息轉(zhuǎn)發(fā)-快速
- Student NSObject forwardingTargetForSelector:
- Student NSObject forwardingTargetForSelector:
//消息轉(zhuǎn)發(fā)-慢速
- Student Student methodSignatureForSelector:
- Student Student methodSignatureForSelector:
...
//還會(huì)調(diào)用一次resolveInstanceMethod滑臊,因?yàn)閙ethodSignatureForSelector返回的簽名需要查找方法來(lái)匹配口芍,所以進(jìn)行l(wèi)ookUpImpOrForward查詢(xún)導(dǎo)致又調(diào)用了resolveInstanceMethod
+ Student NSObject resolveInstanceMethod:
+ Student NSObject resolveInstanceMethod:
//
- Student Student forwardInvocation:

+ NSInvocation NSObject initialize
+ NSInvocation NSInvocation _invocationWithMethodSignature:frame:
- NSMethodSignature NSMethodSignature frameLength
...
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市雇卷,隨后出現(xiàn)的幾起案子鬓椭,更是在濱河造成了極大的恐慌,老刑警劉巖关划,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件小染,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡贮折,警方通過(guò)查閱死者的電腦和手機(jī)裤翩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)调榄,“玉大人踊赠,你說(shuō)我怎么就攤上這事≌窬” “怎么了臼疫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)扣孟。 經(jīng)常有香客問(wèn)我烫堤,道長(zhǎng),這世上最難降的妖魔是什么凤价? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任鸽斟,我火速辦了婚禮,結(jié)果婚禮上利诺,老公的妹妹穿的比我還像新娘富蓄。我一直安慰自己,他們只是感情好慢逾,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布立倍。 她就那樣靜靜地躺著,像睡著了一般侣滩。 火紅的嫁衣襯著肌膚如雪口注。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天君珠,我揣著相機(jī)與錄音寝志,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛材部,可吹牛的內(nèi)容都是我干的毫缆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼乐导,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼苦丁!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起兽叮,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤芬骄,失蹤者是張志新(化名)和其女友劉穎猾愿,沒(méi)想到半個(gè)月后鹦聪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蒂秘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年泽本,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姻僧。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡规丽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出撇贺,到底是詐尸還是另有隱情赌莺,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布松嘶,位于F島的核電站艘狭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏翠订。R本人自食惡果不足惜巢音,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尽超。 院中可真熱鬧官撼,春花似錦、人聲如沸似谁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)巩踏。三九已至秃诵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蛀缝,已是汗流浹背顷链。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嗤练。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓榛了,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親煞抬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子霜大,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容