iOS RunTime之四:消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)三部曲:

接上面消息發(fā)送辽旋,如果當(dāng)前類和父類中都沒有找到實(shí)現(xiàn),那么就會(huì)開始嘗試動(dòng)態(tài)方法解析。

動(dòng)態(tài)方法解析

    //6.IMP沒有找到墓贿,嘗試方法解析一次
    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // 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;
    }

在執(zhí)行了 _class_resolveMethod: 之后储笑,會(huì)跳轉(zhuǎn)到 retry 標(biāo)簽甜熔,重新執(zhí)行查找方法實(shí)現(xiàn)的流程,只不過不會(huì)再調(diào)用 _class_resolveMethod: 方法了突倍,因?yàn)橥ㄟ^ triedResolver 來判斷是否進(jìn)行該類是否進(jìn)行過動(dòng)態(tài)方法解析腔稀。如果首次走到這里,triedResolver = NO羽历,當(dāng)動(dòng)態(tài)方法解析進(jìn)行過一次之后焊虏,會(huì)設(shè)置 triedResolver = YES,這樣下次走到這里的時(shí)候秕磷,就不會(huì)再次進(jìn)行動(dòng)態(tài)方法解析诵闭。

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

_class_resolveMethod 這個(gè)函數(shù)首先判斷是否是 meta-class 類,如果不是元類澎嚣,就執(zhí)行 _class_resolveInstanceMethod疏尿,如果是元類,執(zhí)行 _class_resolveClassMethod易桃。

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

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_class_resolveClassMethod 方法中褥琐,來查詢是否已經(jīng)在運(yùn)行時(shí)將其動(dòng)態(tài)插入類中的實(shí)現(xiàn)函數(shù),如果沒有重新調(diào)用 lookUpImpOrNil 并重新啟動(dòng)緩存晤郑,來判斷是否已經(jīng)添加上 sel 對(duì)應(yīng)的 IMP 指針敌呈,并且重新觸發(fā) objc_msgSend 方法。

id objc_msgSend(id self, SEL _cmd, ...) {
  Class class = object_getClass(self);
  IMP imp = class_getMethodImplementation(class, _cmd);
  return imp ? imp(self, _cmd, ...) : 0;
}

只要一提 objc_msgSend造寝,都會(huì)說它的偽代碼如下或類似的邏輯磕洪,反正就是獲取 IMP 并調(diào)用。

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;
    if (!cls  ||  !sel) return nil;
    imp = lookUpImpOrNil(cls, sel, nil, YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }
    return imp;
}

lookUpImpOrNil 函數(shù)獲取不到 IMP 時(shí)就返回 _objc_msgForward诫龙。

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

lookUpImpOrNil 方法判斷返回 imp 結(jié)果是否和 _objc_msgForward_impcache 相同析显,如果相同返回 nil,反之返回 imp签赃。

回到 lookUpImpOrForward 方法中叫榕,如果也沒有找到 imp 的實(shí)現(xiàn)浑侥,那么method resolver 也沒用了,只能進(jìn)入消息轉(zhuǎn)發(fā)階段晰绎。進(jìn)入這個(gè)階段之前寓落,imp變成 _objc_msgForward_impcache,最后再加入緩存中荞下。

當(dāng)一個(gè)方法沒有實(shí)現(xiàn)時(shí)伶选,可以通過重寫 resolveInstanceMethod:resolveClassMethod: 方法,動(dòng)態(tài)添加未實(shí)現(xiàn)的方法尖昏。其中第一個(gè)是添加實(shí)例方法仰税,第二個(gè)是添加類方法。這兩個(gè)方法都有一個(gè) BOOL 返回值抽诉,返回 NO 則進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制陨簇。

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:sel];
}

objc_msgForward

    STATIC_ENTRY __objc_msgForward_impcache
    // Method cache version

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band condition register is NE for stret, EQ otherwise.

    MESSENGER_START
    nop
    MESSENGER_END_SLOW
    
    jne __objc_msgForward_stret
    //1.跳轉(zhuǎn)到__objc_msgForward
    jmp __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

    ENTRY __objc_msgForward
    // Non-stret version
    //2.執(zhí)行__objc_forward_handler
    movq    __objc_forward_handler(%rip), %r11
    jmp *%r11

    END_ENTRY __objc_msgForward


    ENTRY __objc_msgForward_stret
    // Struct-return version

    movq    __objc_forward_stret_handler(%rip), %r11
    jmp *%r11

    END_ENTRY __objc_msgForward_stret

在執(zhí)行 _objc_msgForward 之后會(huì)調(diào)用 __objc_forward_handler 函數(shù)。

__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;

看源碼實(shí)現(xiàn)當(dāng)我們給一個(gè)對(duì)象發(fā)送一個(gè)沒有實(shí)現(xiàn)的方法的時(shí)候迹淌,如果其父類也沒有這個(gè)方法河绽,則會(huì)崩潰,報(bào)錯(cuò)信息類似于這樣:unrecognized selector sent to instance唉窃,然后接著會(huì)跳出一些堆棧信息耙饰,這些信息就是從這里而來。

重定向

- (id)forwardingTargetForSelector:(SEL)aSelector

當(dāng)動(dòng)態(tài)方法解析不作處理返回 NO 時(shí)纹份,消息轉(zhuǎn)發(fā)機(jī)制會(huì)被觸發(fā)苟跪。在這時(shí)forwardInvocation:方法會(huì)被執(zhí)行。
在消息轉(zhuǎn)發(fā)機(jī)制執(zhí)行前蔓涧,Runtime 系統(tǒng)會(huì)再給我們一次偷梁換柱的機(jī)會(huì)件已,即通過重載 - (id)forwardingTargetForSelector:(SEL)aSelector 方法替換消息的接受者為其他對(duì)象:

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(mysteriousMethod:)){
        return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

轉(zhuǎn)發(fā)

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;

當(dāng) forwardingTargetForSelector: 方法未做出任何響應(yīng)的話,會(huì)來到消息轉(zhuǎn)發(fā)流程元暴。消息轉(zhuǎn)發(fā)時(shí)會(huì)首先調(diào)用 methodSignatureForSelector: 方法篷扩,在方法內(nèi)部生成 NSMethodSignature 類型的方法簽名對(duì)象。在生成簽名對(duì)象時(shí)昨寞,可以指定 targetSEL瞻惋,可以將這兩個(gè)參數(shù)換成其他參數(shù)厦滤,將消息轉(zhuǎn)發(fā)給其他對(duì)象援岩。

[otherObject methodSignatureForSelector:otherSelector];

生成 NSMethodSignature 簽名對(duì)象后,就會(huì)調(diào)用 forwardInvocation: 方法掏导,這是消息轉(zhuǎn)發(fā)中最后一步了享怀,如果在這步還沒有對(duì)消息進(jìn)行處理,則會(huì)導(dǎo)致崩潰趟咆。
該消息的唯一參數(shù)是個(gè) NSInvocation 類型的對(duì)象添瓷,該對(duì)象封裝了原始的消息和消息的參數(shù)梅屉。我們可以實(shí)現(xiàn) forwardInvocation: 方法來對(duì)不能處理的消息做一些默認(rèn)的處理,也可以將消息轉(zhuǎn)發(fā)給其他對(duì)象來處理鳞贷,而不拋出錯(cuò)誤坯汤。

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([object respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:object];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

這里需要注意的是參數(shù) anInvocation 是從哪的來的呢?其實(shí)在 forwardInvocation: 消息發(fā)送前搀愧,Runtime 系統(tǒng)會(huì)向?qū)ο蟀l(fā)送 methodSignatureForSelector: 消息惰聂,并取到返回的方法簽名用于生成 NSInvocation對(duì)象。所以我們?cè)谥貙?forwardInvocation: 的同時(shí)也要重寫 methodSignatureForSelector: 方法咱筛,否則會(huì)拋異常搓幌。

當(dāng)一個(gè)對(duì)象由于沒有相應(yīng)的方法實(shí)現(xiàn)而無(wú)法響應(yīng)某消息時(shí),運(yùn)行時(shí)系統(tǒng)將通過 forwardInvocation: 消息通知該對(duì)象迅箩。每個(gè)對(duì)象都從 NSObject 類中繼承了 forwardInvocation: 方法溉愁。然而,NSObject 中的方法實(shí)現(xiàn)只是簡(jiǎn)單地調(diào)用了 doesNotRecognizeSelector:饲趋。通過實(shí)現(xiàn)我們自己的 forwardInvocation: 方法拐揭,我們可以在該方法實(shí)現(xiàn)中將消息轉(zhuǎn)發(fā)給其它對(duì)象。

forwardInvocation: 方法就像一個(gè)不能識(shí)別的消息的分發(fā)中心篙贸,將這些消息轉(zhuǎn)發(fā)給不同接收對(duì)象投队。或者它也可以象一個(gè)運(yùn)輸站將所有的消息都發(fā)送給同一個(gè)接收對(duì)象爵川。它可以將一個(gè)消息翻譯成另外一個(gè)消息敷鸦,或者簡(jiǎn)單的”吃掉“某些消息,因此沒有響應(yīng)也沒有錯(cuò)誤寝贡。

forwardInvocation: 方法也可以對(duì)不同的消息提供同樣的響應(yīng)扒披,這一切都取決于方法的具體實(shí)現(xiàn)。該方法所提供是將不同的對(duì)象鏈接到消息鏈的能力圃泡。

注意:
forwardInvocation: 方法只有在消息接收對(duì)象中無(wú)法正常響應(yīng)消息時(shí)才會(huì)被調(diào)用碟案。 所以,如果我們希望一個(gè)對(duì)象將 negotiate 消息轉(zhuǎn)發(fā)給其它對(duì)象颇蜡,則這個(gè)對(duì)象不能有 negotiate 方法价说。否則,forwardInvocation: 將不可能會(huì)被調(diào)用风秤。

Paste_Image.png

實(shí)戰(zhàn)

Paste_Image.png

1鳖目、動(dòng)態(tài)解析
我們?cè)?code>Car類的.m文件里面,通過上面介紹動(dòng)態(tài)解析可以知道缤弦,可以重載resolveInstanceMethod:resolveClassMethod:方法分別添加實(shí)例方法實(shí)現(xiàn)和類方法實(shí)現(xiàn)领迈。因?yàn)楫?dāng)Runtime系統(tǒng)在Cache和方法分發(fā)表中找不到要執(zhí)行的方法時(shí),Runtime會(huì)調(diào)用resolveInstanceMethod:resolveClassMethod:來給程序員一次動(dòng)態(tài)添加方法實(shí)現(xiàn)的機(jī)會(huì)。

Paste_Image.png

2狸捅、重定向
我們新建一個(gè)Person類衷蜓,為了讓運(yùn)行時(shí)系統(tǒng)能夠運(yùn)行到forwardingTargetForSelector:方法,我們先在resolveInstanceMethod:中返回NO尘喝,代碼如下:

Paste_Image.png
Paste_Image.png

從運(yùn)行結(jié)果中看出磁浇,我們執(zhí)行[person fly]方法,控制臺(tái)中打出Carrun方法朽褪,最終也實(shí)現(xiàn)了消息的轉(zhuǎn)發(fā)扯夭。

Person *person = [[Person alloc] init];
[person fly];

3、轉(zhuǎn)發(fā)
如果我們都不實(shí)現(xiàn)forwardingTargetForSelector鞍匾,系統(tǒng)就會(huì)方法methodSignatureForSelectorforwardInvocation來實(shí)現(xiàn)轉(zhuǎn)發(fā)交洗,代碼如下:

Paste_Image.png

從運(yùn)行結(jié)果中看出,我們執(zhí)行[person fly]方法橡淑,控制臺(tái)中打出Carrun方法构拳,最終也實(shí)現(xiàn)了消息的轉(zhuǎn)發(fā)。

注意:

  • methodSignatureForSelector用來生成方法簽名梁棠,這個(gè)簽名就是給forwardInvocation中的參數(shù)NSInvocation調(diào)用的置森。
  • unrecognized selector sent to instance,原來就是因?yàn)?code>methodSignatureForSelector這個(gè)方法中符糊,由于沒有找到fly對(duì)應(yīng)的實(shí)現(xiàn)方法凫海,所以返回了一個(gè)空的方法簽名,最終導(dǎo)致程序報(bào)錯(cuò)崩潰男娄。

以上就是消息的轉(zhuǎn)發(fā)行贪,如果有覺得上述我講的不對(duì)的地方歡迎指出,大家多多交流溝通模闲。

參考資料

Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機(jī)制原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末建瘫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子尸折,更是在濱河造成了極大的恐慌啰脚,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件实夹,死亡現(xiàn)場(chǎng)離奇詭異橄浓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)亮航,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門荸实,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人塞赂,你說我怎么就攤上這事泪勒≈缰” “怎么了宴猾?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵圆存,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我仇哆,道長(zhǎng)沦辙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任讹剔,我火速辦了婚禮油讯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘延欠。我一直安慰自己陌兑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布由捎。 她就那樣靜靜地躺著兔综,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狞玛。 梳的紋絲不亂的頭發(fā)上软驰,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音心肪,去河邊找鬼锭亏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛硬鞍,可吹牛的內(nèi)容都是我干的慧瘤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼固该,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼碑隆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蹬音,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤上煤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后著淆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劫狠,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年永部,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了独泞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苔埋,死狀恐怖懦砂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤荞膘,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布罚随,位于F島的核電站,受9級(jí)特大地震影響羽资,放射性物質(zhì)發(fā)生泄漏淘菩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一屠升、第九天 我趴在偏房一處隱蔽的房頂上張望潮改。 院中可真熱鬧,春花似錦腹暖、人聲如沸汇在。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)趾疚。三九已至,卻和暖如春以蕴,著一層夾襖步出監(jiān)牢的瞬間糙麦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工丛肮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赡磅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓宝与,卻偏偏與公主長(zhǎng)得像焚廊,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子习劫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉咆瘟,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,686評(píng)論 0 9
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 749評(píng)論 0 1
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡(jiǎn)介 Runt...
    樂樂的簡(jiǎn)書閱讀 2,132評(píng)論 0 9
  • 目錄 Objective-C Runtime到底是什么 Objective-C的元素認(rèn)知 Runtime詳解 應(yīng)用...
    Ryan___閱讀 1,935評(píng)論 1 3
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí),它使得 Objective-C 如虎添翼诽里,具備了靈活的...
    lylaut閱讀 795評(píng)論 0 4