OC底層原理-動(dòng)態(tài)方法決議

當(dāng)lookupImpOrForward函數(shù)從cache和methodTable中找不到對(duì)應(yīng)Method楚里,繼續(xù)向下執(zhí)行就會(huì)來(lái)到resolveMethod_locked函數(shù)也就是我們常說(shuō)的動(dòng)態(tài)方法決議

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

resolveMethod_locked

    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);

只執(zhí)行一次

behavior & LOOKUP_RESOLVER
behavior ^= LOOKUP_RESOLVER;
這倆步操作保證resolveMethod_locked只被執(zhí)行一次

resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
       //根類NSObject有默認(rèn)實(shí)現(xiàn)兜底叔磷,不會(huì)走到這里
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
   //向cls對(duì)象發(fā)送resolveInstanceMethod:消息外傅,參數(shù)為當(dāng)前的sel
    bool resolved = msg(cls, resolve_sel, 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 = lookUpImpOrNilTryCache(inst, sel, cls);

    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));
        }
    }
}

resolveClassMethod

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
       // NSObject有兜底實(shí)現(xiàn)
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(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 = lookUpImpOrNilTryCache(inst, sel, cls);

    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));
        }
    }
}

resolveMethod_locked會(huì)發(fā)送resolveInstanceMethod:和resolveClassMethod:消息斤儿,為了減少程序的崩潰提用戶體驗(yàn)吱瘩,蘋果在這里給開發(fā)者一次機(jī)會(huì)去補(bǔ)救壁熄,這個(gè)過(guò)程就叫做動(dòng)態(tài)方法決議敲董。這里也體現(xiàn)了aop編程思想,在objc_msg流程中給開發(fā)者提供了一個(gè)切面空另,切入自己想要處理盆耽,比如安全處理,日志收集等等扼菠。

resolveClassMethod

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(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 = lookUpImpOrNilTryCache(inst, sel, cls);

    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));
        }
    }
}

看完源碼思考倆個(gè)問(wèn)題

1.為什么在resloveInstanceMethod函數(shù)中調(diào)用了一次lookUpImpOrNilTryCache摄杂,resolveMethod_locked函數(shù)最后又調(diào)用了一次lookUpImpOrNilTryCache?這倆次分別有什么作用循榆?

  • 第一次TryCache流程分析

堆棧信息-->第一次tryCache會(huì)把我動(dòng)態(tài)添加的方法存進(jìn)cache
截屏2021-07-01 下午4.21.41.png

本次TryCache析恢,會(huì)調(diào)用lookUpImpOrForWard函數(shù)查找MethodTable。入?yún)ehavior值為4秧饮,找不到imp的話不會(huì)再走動(dòng)態(tài)決議和消息轉(zhuǎn)發(fā)映挂,直接return nil,分支如下:

    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }

所以這次tryCache實(shí)際的作用就是在動(dòng)態(tài)決議添加方法之后浦楣,找到方法袖肥,并調(diào)用log_and_fill_cache函數(shù)存進(jìn)緩存(佐證了下面這段注釋)

   // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
  • 第二次TryCache流程分析

這次我們直接看注釋吧

    // chances are that calling the resolver have populated the cache
    // so attempt using it

調(diào)用動(dòng)態(tài)決議可能填充了得緩存,并嘗試使用它振劳。嗯椎组,第二次tryCache的作用已經(jīng)簡(jiǎn)單明了。
本次調(diào)用入?yún)ehavior值為1历恐,methodTable查找不到imp不會(huì)走動(dòng)態(tài)決議流程寸癌,但會(huì)調(diào)用消息轉(zhuǎn)發(fā)

  • 為什么分為倆次呢专筷,一次不行嗎?

為什么不最后查找方法蒸苇,填充緩存再返回磷蛹,反而要先填充緩存,再嘗試從緩存中查找溪烤,這么做有什么好處呢味咳?

有個(gè)關(guān)于多線程的猜想:

假如線程a發(fā)送消息s進(jìn)入了動(dòng)態(tài)決議流程,此時(shí)線程b也發(fā)送消息s檬嘀,這時(shí)候如果緩存中有已添加的imp響應(yīng)消息s槽驶,是不是就不會(huì)繼續(xù)慢速查找,動(dòng)態(tài)決議等后續(xù)流程鸳兽。這么想,動(dòng)態(tài)決議添加的方法是不是越先添加到緩存越好掂铐。

另外一點(diǎn)我們看到resolveClassMethod之后,也嘗試從緩存中查找,而且找不到又調(diào)用了一遍resolveInstanceMethod揍异。

可已看出蘋果開發(fā)者在設(shè)計(jì)這段流程的思考??可能是:
既然你愿意通過(guò)動(dòng)態(tài)方法決議去添加這個(gè)imp全陨,費(fèi)了這么大功夫,很顯然你想使用該imp衷掷,而且使用的頻率可能不低辱姨。既然如此在resolver方法調(diào)用完畢,我就幫你放進(jìn)緩存吧棍鳖。以后你想用直接從緩存中找炮叶。

2. 為什么類resolver之后會(huì)嘗試調(diào)用instance的resolver碗旅?難道instance的resolver還能解決類方法缺失的問(wèn)題渡处?

關(guān)于這個(gè)問(wèn)題,我們來(lái)看張經(jīng)典的
isa流程圖.png

如果我們查找一個(gè)類方法沿著繼承鏈最終會(huì)找到NSObject(rootMetaClass的父類是NSObject)祟辟,這會(huì)導(dǎo)致一個(gè)有意思的問(wèn)題:我們的NSObject對(duì)象方法可以響應(yīng)類方法的sel

看個(gè)實(shí)例

給NSObect添加個(gè)instaceMethod


截屏2021-07-02 上午9.53.58.png

發(fā)送個(gè)類方法消息
截屏2021-07-02 上午9.55.53.png

是不是很驚喜医瘫,其實(shí)我們底層對(duì)classMethod和InstanceMethod根本沒(méi)有區(qū)分,classMethod也是InstanceMethod

* class_getClassMethod.  Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

只不過(guò)旧困,找classMethod是從MetaClass查找InstanceMethod醇份,找InstanceMethod是從class找InstanceMethod。
透過(guò)現(xiàn)象看本質(zhì)吼具,這里就可以解釋僚纷,為什么resolveClass完畢,緩存中找不到imp拗盒,會(huì)再次調(diào)用resolveInstance怖竭。顯然,我們給NSObject添加InstanceMethod可以解決問(wèn)題陡蝇,而且可以在這里我們也可以添加classMethod痊臭。畢竟classMethod也是InstanceMethod哮肚。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市广匙,隨后出現(xiàn)的幾起案子允趟,更是在濱河造成了極大的恐慌,老刑警劉巖鸦致,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潮剪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡分唾,警方通過(guò)查閱死者的電腦和手機(jī)鲁纠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鳍寂,“玉大人改含,你說(shuō)我怎么就攤上這事∑矗” “怎么了捍壤?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鞍爱。 經(jīng)常有香客問(wèn)我鹃觉,道長(zhǎng),這世上最難降的妖魔是什么睹逃? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任盗扇,我火速辦了婚禮,結(jié)果婚禮上沉填,老公的妹妹穿的比我還像新娘疗隶。我一直安慰自己,他們只是感情好翼闹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布斑鼻。 她就那樣靜靜地躺著,像睡著了一般猎荠。 火紅的嫁衣襯著肌膚如雪坚弱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天关摇,我揣著相機(jī)與錄音荒叶,去河邊找鬼。 笑死输虱,一個(gè)胖子當(dāng)著我的面吹牛些楣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼戈毒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼艰猬!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起埋市,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤冠桃,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后道宅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體食听,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年污茵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了樱报。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泞当,死狀恐怖迹蛤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情襟士,我是刑警寧澤盗飒,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站陋桂,受9級(jí)特大地震影響逆趣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嗜历,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一宣渗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梨州,春花似錦痕囱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至巷查,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抹腿,已是汗流浹背岛请。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留警绩,地道東北人崇败。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親后室。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缩膝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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