OC底層探索12-消息動(dòng)態(tài)決議,方法慢速、快速轉(zhuǎn)發(fā)

OC底層探索11-objc_msgSend慢速查找流程中解釋了對(duì)方法的非緩存查詢以及方法查找失敗之后的系統(tǒng)報(bào)錯(cuò)阅签。

如果在2種機(jī)制下都沒(méi)有找到方法imp,蘋果也給出了2條建議:

  • 動(dòng)態(tài)方法決議:慢速查找流程未找到后蝎抽,會(huì)執(zhí)行一次動(dòng)態(tài)方法決議resolveMethod_locked
  • 消息轉(zhuǎn)發(fā):如果動(dòng)態(tài)方法決議仍然沒(méi)有找到實(shí)現(xiàn)政钟,則進(jìn)行消息轉(zhuǎn)發(fā)

1. 方法動(dòng)態(tài)決議

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
    // 動(dòng)態(tài)方法決議 : 給一次機(jī)會(huì) 重新查詢
    if (! cls->isMetaClass()) {  // 對(duì)象 - 類
        resolveInstanceMethod(inst, sel, cls);
    } 
    else { // 類方法 - 元類
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) { 
        // 此處cls已經(jīng)是元類了。元類調(diào)用方法樟结,根據(jù)isa關(guān)系會(huì)在根元類中查找方法养交。
        //通過(guò)根元類的繼承鏈最終找到NSObject,在NSObject中查詢`實(shí)例方法`的resolve的實(shí)現(xiàn)瓢宦。巧妙的設(shè)計(jì)K榱!
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // 在調(diào)用一次查詢
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
  • 此處的cls已經(jīng)是指向isa的指針驮履,也就是,元類,此邏輯是經(jīng)過(guò)匯編層的加工鱼辙。所以判斷不是元類就調(diào)用實(shí)例方法的resolve實(shí)現(xiàn);反之調(diào)用類方法的resolve實(shí)現(xiàn).
  • 根據(jù)元類的iSA關(guān)系廉嚼,最終會(huì)找到根元類(NSObject),因?yàn)轭惖母惗际?code>NSObject倒戏。但(NSObject)類中只存在對(duì)象方法怠噪,所以需要再調(diào)用一次resolveInstanceMethod
  • 在方法動(dòng)態(tài)決議中杜跷,開(kāi)發(fā)者會(huì)重新實(shí)現(xiàn)該selimp所以傍念, 需要重新進(jìn)行一次查詢。而且在本次查詢中會(huì)優(yōu)先在緩存中查找葱椭。
resolveInstanceMethod

實(shí)例方法的resolve具體源碼實(shí)現(xiàn)

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    //生成動(dòng)態(tài)解析的方法Sel
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    //在當(dāng)前類的元類中查找resolve方法是否找得到
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        return;
    }
    
    //resolveInstanceMethod消息發(fā)送
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    //進(jìn)行一次方法慢速查詢捂寿,將當(dāng)前方法插入緩存中口四,提高效率
    IMP imp = lookUpImpOrNil(inst, sel, cls);
    //在實(shí)現(xiàn)且需要打印是孵运,進(jìn)行打印
    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 {
            _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));
        }
    }
}
  • resolveInstanceMethod調(diào)用后,又調(diào)用了一次lookUpImpOrNil蔓彩;我們知道該方法如果找到對(duì)應(yīng)imp之后會(huì)插入到對(duì)象類的緩存中去治笨,方便后續(xù)使用;另一個(gè)是方便debug進(jìn)行打印赤嚼。

如何打開(kāi)該打印
可以通過(guò)查看log來(lái)發(fā)現(xiàn)實(shí)現(xiàn)了resolveInstanceMethod的方法

resolveClassMethod

類方法的resolve具體源碼實(shí)現(xiàn)

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
    //判斷resolveClassMethod方法是否實(shí)現(xiàn)
    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        return;
    }
    //判斷類是否實(shí)現(xiàn)
    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        //根據(jù)元類找到類
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    //resolveClassMethod消息發(fā)送
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    //進(jìn)行一次方法慢速查詢旷赖,將當(dāng)前方法插入緩存中,提高效率
    IMP imp = lookUpImpOrNil(inst, sel, cls);
    
    //在實(shí)現(xiàn)且需要打印是更卒,進(jìn)行打印
    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));
        }
    }
}

  • 實(shí)現(xiàn)邏輯于resolveInstanceMethod基本相同
使用方式
  1. 實(shí)例方法的resolve
    為self動(dòng)態(tài)增加sayMaster的實(shí)現(xiàn).使用runtime-api
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
    Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
    const char *type  = method_getTypeEncoding(sayMMethod);
    return class_addMethod(self, sel, imp, type);
}
  1. 類方法的resolve
+ (BOOL)resolveClassMethod:(SEL)sel{
    IMP imp           = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
}
  • 唯一的區(qū)別就是類方法是需要添加到元類中的等孵,所以需要先找到元類objc_getMetaClass.
根據(jù)觀察resolveInstanceMetho會(huì)走2次
  1. 第一次是在查詢方法時(shí)lookupimp中調(diào)用的

  2. 第二次是在coreFunction時(shí)調(diào)用的


  • 在慢速轉(zhuǎn)發(fā)過(guò)程中會(huì)進(jìn)行第二次調(diào)用,后面會(huì)換種方式來(lái)驗(yàn)證

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

在之前有提到apple推薦的快速轉(zhuǎn)發(fā)蹂空、慢速轉(zhuǎn)發(fā)俯萌,他們是何時(shí)調(diào)用的呢?是以什么方式調(diào)用的呢上枕?現(xiàn)在就來(lái)討論下~

方法一
不知在之前有沒(méi)有留意一個(gè)方法log_and_fill_cache(...)在這個(gè)方法中我們發(fā)現(xiàn)了一個(gè)系統(tǒng)提供的log方法咐熙。
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill(cls, sel, imp, receiver);
}
  • 是否打印就是看objcMsgLogEnabled這個(gè)參數(shù)的值,系統(tǒng)并沒(méi)有對(duì)外提供這個(gè)方法辨萍,但是我們可以自己導(dǎo)出.
調(diào)用方法
  • extern void instrumentObjcMessageSends(BOOL flag);這個(gè)方法就是系統(tǒng)提供棋恼,但是需要我們手動(dòng)導(dǎo)出后使用的方法。
  • 在我們調(diào)用方法的前后進(jìn)行锈玉,可以看到該方法的所有調(diào)用記錄
如何查看
結(jié)果

看到了熟悉的resolveInstanceMethod爪飘,而且出現(xiàn)了2次,也印證了之前的猜測(cè)拉背。
與此同時(shí)還有些并不熟悉的方法forwardingTargetForSelector,methodSignatureForSelector师崎。

方法二

看到了這個(gè)調(diào)用的堆棧信息,調(diào)用的是CoreFoundation庫(kù)∪ネ牛可是apple爸爸并沒(méi)有開(kāi)源這個(gè)庫(kù)抡诞,所以想要查看內(nèi)部的調(diào)用就需要拿出最終大招Hopper反匯編穷蛹。

  • 在lldb調(diào)試中使用image list查看CoreFoundation的庫(kù)的本地地址。然后拖入Hopper中昼汗。
  • forwarding在可以看到forwardingTargetForSelector的偽代碼調(diào)用
  • 這就是所謂快速轉(zhuǎn)發(fā)流程

  • 繼續(xù)跟流程會(huì)走到methodSignatureForSelector肴熏,以及forwardInvocation
  • 這就是所謂慢速轉(zhuǎn)發(fā)流程
  • 查看調(diào)用棧這兩個(gè)方法中間應(yīng)該會(huì)調(diào)用resolveInstanceMethod,但是在反匯編中沒(méi)有看到具體的調(diào)用,如果有知道的大佬可以提醒一下小弟顷窒。
消息轉(zhuǎn)發(fā)簡(jiǎn)單實(shí)現(xiàn)
// 1: 快速轉(zhuǎn)發(fā)
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    //此處返回一個(gè)實(shí)現(xiàn)該方法sel的對(duì)象
    return [super forwardingTargetForSelector:aSelector];
}

// 2: 慢速轉(zhuǎn)發(fā)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    //返回方法的參數(shù)編碼
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    //更換方法接受者
    anInvocation.target = [LGStudent alloc];
    //更換方法索引
    anInvocation.selector = NSSelectorFromString(@"實(shí)現(xiàn)了該IMP的SEL");
    //更換方法參數(shù)編碼
    anInvocation.methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"]
    // anInvocation 保存 - 方法
    [anInvocation invoke];
}

3. 整體流程圖

消息實(shí)現(xiàn)未找到后的流程
  • forwardInvocation不處理并不會(huì)奔潰蛙吏。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鞋吉,隨后出現(xiàn)的幾起案子鸦做,更是在濱河造成了極大的恐慌,老刑警劉巖谓着,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泼诱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡赊锚,警方通過(guò)查閱死者的電腦和手機(jī)治筒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)舷蒲,“玉大人耸袜,你說(shuō)我怎么就攤上這事∩剑” “怎么了堤框?”我有些...
    開(kāi)封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)纵柿。 經(jīng)常有香客問(wèn)我蜈抓,道長(zhǎng),這世上最難降的妖魔是什么藐窄? 我笑而不...
    開(kāi)封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任资昧,我火速辦了婚禮,結(jié)果婚禮上荆忍,老公的妹妹穿的比我還像新娘格带。我一直安慰自己,他們只是感情好刹枉,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布叽唱。 她就那樣靜靜地躺著,像睡著了一般微宝。 火紅的嫁衣襯著肌膚如雪棺亭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天蟋软,我揣著相機(jī)與錄音镶摘,去河邊找鬼嗽桩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凄敢,可吹牛的內(nèi)容都是我干的碌冶。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼涝缝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扑庞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起拒逮,我...
    開(kāi)封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤罐氨,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后滩援,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體栅隐,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年狠怨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了约啊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邑遏。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡佣赖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出记盒,到底是詐尸還是另有隱情憎蛤,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布纪吮,位于F島的核電站俩檬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏碾盟。R本人自食惡果不足惜棚辽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冰肴。 院中可真熱鬧屈藐,春花似錦、人聲如沸熙尉。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)检痰。三九已至包归,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铅歼,已是汗流浹背公壤。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工换可, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厦幅。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓锦担,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親慨削。 傳聞我的和親對(duì)象是個(gè)殘疾皇子洞渔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

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