iOS-OC底層原理_動(dòng)態(tài)方法決議及消息轉(zhuǎn)發(fā)

前言

在之前的的objc_msgSend()探索中,當(dāng)調(diào)用一個(gè)方法后乍桂,首先會(huì)進(jìn)入快速查找來匹配sel對(duì)應(yīng)的imp冲杀;當(dāng)沒有找到時(shí),會(huì)進(jìn)入慢速查找模蜡,開始匹配漠趁;當(dāng)依然無法匹配時(shí),蘋果給出了一次轉(zhuǎn)發(fā)的機(jī)會(huì)(動(dòng)態(tài)方法決議):

// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
    behavior ^= LOOKUP_RESOLVER;
    return resolveMethod_locked(inst, sel, cls, behavior);
}

從注釋:// No implementation found. Try method resolver once.也是一目了然忍疾。
這篇文章闯传,我們就一起探索下resolveMethod_locked()的內(nèi)部實(shí)現(xiàn)。

開始

直接看下resolveMethod_locked()的源碼:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    // 方法沒有你怎么不知道
    // 報(bào)錯(cuò)
    // 給你一次機(jī)會(huì)
    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 (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

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

首先判斷當(dāng)前的cls是否為元類,當(dāng)不是元類則調(diào)用resolveInstanceMethod(),否則調(diào)用resolveClassMethod()卤妒。接下來分別看下兩個(gè)方法的具體實(shí)現(xiàn):
static void resolveInstanceMethod(id inst, SEL sel, Class cls)

/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    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 = lookUpImpOrNil(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));
        }
    }
}

該方法一開始的注釋寫的就很清楚:

  • Call +resolveInstanceMethod, looking for a method to be added to class cls.

意味著甥绿,resolveInstanceMethod執(zhí)行會(huì)響應(yīng)+resolveInstanceMethod,我們可以通過在類中實(shí)現(xiàn)該方法字币,來接收響應(yīng),并做處理共缕。同時(shí)我們可以看到發(fā)起響應(yīng)的關(guān)鍵代碼:

SEL resolve_sel = @selector(resolveInstanceMethod:);
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

即通過objc_msgSendcls發(fā)送了一個(gè)resolveInstanceMethod消息洗出。
static void resolveClassMethod(id inst, SEL sel, Class cls)

//在cls中添加并實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    //自定義動(dòng)態(tài)處理
   return [super resolveInstanceMethod:sel];
}

再來看下 resolveClassMethod的具體實(shí)現(xiàn)

/***********************************************************************
* 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 resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNil(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 = lookUpImpOrNil(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));
        }
    }
}

同樣在方法的注釋里面也寫的很清楚,在cls中添加并實(shí)現(xiàn)該方法來接收響應(yīng)图谷,關(guān)鍵的觸發(fā)響應(yīng)代碼翩活,同樣是通過objc_msgSend發(fā)送消息,跟resolveInstanceMethod的邏輯如出一轍:

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

回到我們的類:

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
  //添加自定義處理
   return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
  //添加自定義處理
   return [super resolveClassMethod:sel];
}

消息轉(zhuǎn)發(fā)

當(dāng)方法決議后依然沒有匹配到imp時(shí)便贵,會(huì)觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制菠镇,由libObjC轉(zhuǎn)向CoreFoundation處理并觸發(fā)轉(zhuǎn)發(fā)回調(diào),這個(gè)可以在Crash時(shí)的堆棧信息中看到:

截屏2020-10-15 下午2.54.31.png

關(guān)于CoreFoundation的源碼這里就不展開驗(yàn)證了(需要借助一些反編譯工具查看一些偽代碼)承璃。
此時(shí)會(huì)觸發(fā)以下幾個(gè)熟悉的回調(diào)函數(shù):

//快速轉(zhuǎn)發(fā)流程
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return nil;
}
//慢速轉(zhuǎn)發(fā)流程
//1.方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
//2.轉(zhuǎn)發(fā)處理
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//    [anInvocation invoke];
}
//當(dāng)沒有實(shí)現(xiàn)任何處理時(shí)
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    
}

在日常的開發(fā)中利耍,我們可以在適當(dāng)?shù)奈恢茫鲰憫?yīng)的容錯(cuò)處理代碼盔粹。

消息轉(zhuǎn)發(fā)簡(jiǎn)單流程圖

消息轉(zhuǎn)發(fā)流程.008.jpeg

這里解釋下resolveClassMethod->resolveInstanceMethod:
當(dāng)調(diào)用類方法的時(shí)候隘梨,Objc會(huì)去該類元類中去查找對(duì)應(yīng)的IMP,沒有找到會(huì)去他的父元類中查找,最終會(huì)查詢到根元類舷嗡,根元類依然沒有找到轴猎,會(huì)繼續(xù)去根元類的父類,即根類(NSObject)中進(jìn)行查找咬崔,而類中只有實(shí)例方法税稼,此時(shí)objc內(nèi)部做了一些處理,當(dāng)在根元類無法找到IMP時(shí)垮斯,直接響應(yīng)父類(NSObject類)resolveInstanceMethod方法郎仆,可以在如下的源碼中體現(xiàn):

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    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 (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
總結(jié)

至此,我們能看到兜蠕,在我們程序?qū)⒁l(fā)生崩潰時(shí)扰肌,我們有兩次避免崩潰的機(jī)會(huì),即動(dòng)態(tài)方法決議消息轉(zhuǎn)發(fā)處理熊杨,前者是在libObjC下觸發(fā)曙旭,或者在CoreFoundation下觸發(fā)。在實(shí)際開發(fā)中我們應(yīng)該盡量避免在方法決議層做邏輯處理晶府,而是在更底層的消息轉(zhuǎn)發(fā)層做相應(yīng)的容錯(cuò)處理桂躏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市川陆,隨后出現(xiàn)的幾起案子剂习,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳞绕,死亡現(xiàn)場(chǎng)離奇詭異失仁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)们何,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門萄焦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冤竹,你說我怎么就攤上這事拂封。” “怎么了贴见?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵烘苹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我片部,道長(zhǎng),這世上最難降的妖魔是什么霜定? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任档悠,我火速辦了婚禮,結(jié)果婚禮上望浩,老公的妹妹穿的比我還像新娘辖所。我一直安慰自己,他們只是感情好磨德,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布缘回。 她就那樣靜靜地躺著,像睡著了一般典挑。 火紅的嫁衣襯著肌膚如雪酥宴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天您觉,我揣著相機(jī)與錄音拙寡,去河邊找鬼。 笑死琳水,一個(gè)胖子當(dāng)著我的面吹牛肆糕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播在孝,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诚啃,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了私沮?” 一聲冷哼從身側(cè)響起始赎,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后极阅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胃碾,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年筋搏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仆百。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奔脐,死狀恐怖俄周,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情髓迎,我是刑警寧澤峦朗,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站排龄,受9級(jí)特大地震影響波势,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜橄维,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一尺铣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧争舞,春花似錦凛忿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至委乌,卻和暖如春床牧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背福澡。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工叠赦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人革砸。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓除秀,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親算利。 傳聞我的和親對(duì)象是個(gè)殘疾皇子册踩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355