iOS底層-runtime應(yīng)用之方法交換(method swizzling)

method swizzling 就是常說(shuō)的方法交換李请,也常被成為黑魔法毫玖。
簡(jiǎn)單點(diǎn)說(shuō)就是 定義并實(shí)現(xiàn)了方法A和B,在調(diào)用方法A的時(shí)候叠艳,執(zhí)行的確實(shí)方法B

基本概念

在了解method swizzling之前先來(lái)了解幾個(gè)概念:

SEL/@selector(方法名)

SEL 又叫選擇器烁登,但是一般我們將它稱(chēng)之為方法編號(hào)怯屉。源碼中的定義為
typedef struct objc_selector *SEL;

以下關(guān)于方法編號(hào)的解釋來(lái)自這篇文章 (未得專(zhuān)業(yè)驗(yàn)證)

方法以 selector 作為索引. selector 的數(shù)據(jù)類(lèi)型是 SEL. 雖然 SEL 定義成 char*, 我們可 以把它想象成 int. 每個(gè)方法的名字對(duì)應(yīng)一個(gè)唯一的 int 值.比如, 方法 addObject: 可能 對(duì)應(yīng)的是 12. 當(dāng)尋找該方法是, 使用的是 selector,而不是名字 @"addObject:"

Objective-C 數(shù)據(jù)結(jié)構(gòu)中,存在一個(gè) name - selector 的映射表如下圖


映射關(guān)系

在編譯的時(shí)候, 只要有方法的調(diào)用, 編譯器都會(huì)通過(guò) selector 來(lái)查找,所以 (假設(shè) addObject 的 selector 為 12)

[myObject addObject:yourObject];
將會(huì)編譯變成

objc_msgSend(myObject, 12, yourObject);

這里,objec_msgSend()函數(shù)將會(huì)使用 myObjec 的 isa 指針來(lái)找到 myObject 的類(lèi)空間結(jié)構(gòu)并 在類(lèi)空間結(jié)構(gòu)中查找 selector 12 所對(duì)應(yīng)的方法.如果沒(méi)有找到,那么將使用指向父類(lèi)的指 針找到父類(lèi)空間結(jié)構(gòu)進(jìn)行 selector 12 的查找. 如果仍然沒(méi)有找到,就繼續(xù)往父類(lèi)的父類(lèi)一 直找,直到找到為止, 如果到了根類(lèi) NSObject 中仍然找不到,將會(huì)拋出異常.

我們可以看到, 這是一個(gè)很動(dòng)態(tài)的查找過(guò)程.類(lèi)的結(jié)構(gòu)可以在運(yùn)行的時(shí)候改變,這樣可以很 容易來(lái)進(jìn)行功能擴(kuò)展Objective-C 語(yǔ)言是動(dòng)態(tài)語(yǔ)言, 支持動(dòng)態(tài)綁定.

??文章摘要結(jié)束

IMP

IMP指向方法實(shí)現(xiàn)的首地址,類(lèi)似C語(yǔ)言的函數(shù)指針饵沧。IMP是消息最終調(diào)用的執(zhí)行代碼锨络,是方法真正的實(shí)現(xiàn)代碼 。
源碼中的定義:
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif

每一個(gè)實(shí)現(xiàn)了的方法都存在一個(gè)SEL和IMP(這不廢話狼牺,不然怎么可以成功調(diào)用羡儿?),寫(xiě)句的原因是可能剛理解這些概念的時(shí)候有些繞:

只有聲明是钥,沒(méi)有實(shí)現(xiàn)的方法 有沒(méi)有SEL/IMP掠归?比如只是在.h文件中寫(xiě)入- (void)test?
只有實(shí)現(xiàn)沒(méi)有聲明的方法,有沒(méi)有SEL/IMP咏瑟? 比如只是在.m文件中寫(xiě)- (void)test{}?

上面問(wèn)題的答案就是:
只有聲明拂到,沒(méi)有實(shí)現(xiàn):SEL和IMP都沒(méi)有
沒(méi)有聲明痪署,只有實(shí)現(xiàn):SEL和IMP都有码泞。
在只有聲明,沒(méi)有實(shí)現(xiàn)的情況下狼犯,打印類(lèi)結(jié)構(gòu)信息余寥。在ro里面的信息是 baseMethodList = 0x0000000000000000

Method

主要包含三部分

方法名:方法名為此方法的簽名,有著相同函數(shù)名和參數(shù)名的方法有著相同的方法名悯森。
方法類(lèi)型:方法類(lèi)型描述了參數(shù)的類(lèi)型宋舷。
IMP: IMP即函數(shù)指針,為方法具體實(shí)現(xiàn)代碼塊的地址瓢姻,可像普通C函數(shù)調(diào)用一樣使用IMP祝蝠。
實(shí)際上相當(dāng)于在SEL和IMP之間作了一個(gè)映射。有了SEL,我們便可以找到對(duì)應(yīng)的IMP绎狭。
源碼中的定義:
struct objc_method {
SEL _Nonnull method_name;
char * _Nullable method_types;
IMP _Nonnull method_imp;
}

method swizzling 的應(yīng)用

在實(shí)際開(kāi)發(fā)中细溅,經(jīng)常會(huì)遇到這樣的情況

NSArray *array = @[@"1",@"2",@"3"];
NSLog(@"%@",array[4]);

一般來(lái)說(shuō),在使用下標(biāo)取值之前儡嘶,都需要先判斷
if (array.count < 4) {
NSLog(@"%@",array[4]);
}
但是總有漏掉判斷或其他情況導(dǎo)致crash
為了避免這種情況或者少寫(xiě)重復(fù)代碼喇聊,我們可以使用動(dòng)態(tài)方法交換的方式來(lái)處理
定義一個(gè)方法JERuntimeTool
在.h中定義方法并在.m中實(shí)現(xiàn)

#import <objc/runtime.h>
/**
交換方法

@param cls 交換對(duì)象
@param oriSEL 原始方法編號(hào)
@param swizzledSEL 交換的方法編號(hào)
*/
+ (void)je_methodSwizzlingWithClass:(Class)cls
                             oriSEL:(SEL)oriSEL
                        swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls){
        NSLog(@"傳入的交換類(lèi)不能為空");
        return;
    }
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    method_exchangeImplementations(oriMethod, swiMethod);
}

定義一個(gè)拓展NSArray的JE類(lèi)NSArray+JE.h

針對(duì)下標(biāo)取值的情況,有多個(gè)方法蹦狂,都需要進(jìn)行異常捕獲

- (id)je_objectAtIndex:(NSUInteger)index{
    if (index > self.count-1) {
        //異常處理或記錄打印
        return nil;
    }
    return [self lg_objectAtIndex:index];
}

- (id)je_objectAtIndexedSubscript:(NSUInteger)index{
     if (index > self.count-1) {
        //異常處理或記錄打印
        return nil;
    }
    return [self lg_objectAtIndexedSubscript:index];
}

進(jìn)行方法交換

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        [JERuntimeTool je_methodSwizzlingWithClass:objc_getClass("__NSArrayI")
                                            oriSEL:@selector(objectAtIndex:)
                                       swizzledSEL:@selector(je_objectAtIndex:)];
        
        [JERuntimeTool je_methodSwizzlingWithClass:objc_getClass("__NSArrayI")
                                            oriSEL:@selector(objectAtIndexedSubscript:)
                                       swizzledSEL:@selector(je_objectAtIndexedSubscript:)];
    });
}

這里有幾點(diǎn)需要注意:
1誓篱、在+ (void)load 方法中調(diào)用
2、調(diào)用的時(shí)候凯楔,用單例的方式
3窜骄、對(duì)于系統(tǒng)的有些方法應(yīng)該交換哪個(gè)方法?
4摆屯、交換的類(lèi)應(yīng)該是那個(gè)啊研?
在crash的時(shí)候有一個(gè)錯(cuò)誤信息,其中有一段:reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 4 beyond bounds [0 .. 2],其中 __NSArrayI是需要交換的類(lèi)鸥拧, objectAtIndexedSubscript:是需要交換的方法党远。
對(duì)于我們自定義的類(lèi)一般來(lái)說(shuō)直接就是類(lèi)名和自定義的方法SEL,但是系統(tǒng)的抽象類(lèi)類(lèi)是不能直接作為類(lèi)對(duì)象傳入的富弦,比如NSArray/NSMutableArrya沟娱、NSDictionary/NSMutableDictionary、 NSData/NSMutableData等腕柜。

上面的方法看似很完美济似,但是下面坑就是在你不經(jīng)意間就出現(xiàn)在腳下。

會(huì)出現(xiàn)的問(wèn)題

問(wèn)題1

背景:子類(lèi)沒(méi)有實(shí)現(xiàn)父類(lèi)的方法 但是對(duì)子類(lèi)的方法進(jìn)行了交換
比如:Person:NSObjec Student:Person
Person定義并實(shí)現(xiàn)了方法:-(void)personMethod;
Studen 沒(méi)有實(shí)現(xiàn) -(void)personMethod;

在Student的分類(lèi)中中交換方法

Student+JE.h

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [JERuntimeTool je_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(je_studentInstanceMethod)];
    });
}

- (void)je_studentInstanceMethod{
    NSLog(@"do something");
    [self je_studentInstanceMethod];
}

執(zhí)行代碼

Student *s = [Student new];
[s personInstanceMethod];
    
Person *p = [Person new];
[p personInstanceMethod];

運(yùn)行結(jié)果:

do something
person對(duì)象方法:-[Person personInstanceMethod]

do something
[Person je_studentInstanceMethod]: unrecognized selector sent to instance 0x600002e5c440

在person調(diào)用 personInstanceMethod 方法的時(shí)候 出現(xiàn)了問(wèn)題盏缤,原因是:

1砰蠢、person 調(diào)用 personInstanceMethod(SEL) ,由于進(jìn)行了交換唉铜,將執(zhí)行
je_studentInstanceMethod(IMP)
2台舱、繼續(xù)調(diào)用[self je_studentInstanceMethod],person 調(diào)用 je_studentInstanceMethod (SEL)
3、由于je_studentInstanceMethod(SEL) 屬于Student(調(diào)用SEL需要執(zhí)行相對(duì)應(yīng)的IMP潭流,這個(gè)時(shí)候竞惋,在Person中沒(méi)有相應(yīng)的IMP) 所以person 調(diào)用的時(shí)候 出現(xiàn)了 [Person je_studentInstanceMethod]: unrecognized的錯(cuò)誤

知道了原因,我們需要針對(duì)性解決問(wèn)題:
在Student中添加一個(gè) 方法灰嫉,對(duì)應(yīng)關(guān)系為:swizzleSEL + Ori IMP拆宛。(可見(jiàn)下圖)
在這里,采用的是 :向Student中添加
je_studentInstanceMethod(SEL) + personInstanceMethod(IMP)讼撒,如果添加成功浑厚,說(shuō)明Student沒(méi)有實(shí)現(xiàn)這個(gè)方法股耽,這樣來(lái)達(dá)到通用性,代碼如下

+ (void)je_methodSwizzlingWithClass:(Class)cls
                             oriSEL:(SEL)oriSEL
                        swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls){
        NSLog(@"傳入的交換類(lèi)不能為空");
        return;
    }
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
}

執(zhí)行步驟如下圖


交換代碼中的執(zhí)行過(guò)程

問(wèn)題2

背景:父類(lèi)和子類(lèi)都沒(méi)有實(shí)現(xiàn)
比如:子類(lèi)Student 只申明一個(gè)方法readBook钳幅;

在Student的分類(lèi)中中交換方法

Student+JE.h

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [JERuntimeTool je_methodSwizzlingWithClass:self oriSEL:@selector(readBook) swizzledSEL:@selector(je_readBook)];
    });
}

- (void)je_readBook{
    NSLog(@"do something");
    [self je_readBook];
}

執(zhí)行代碼

Student *s = [Student new];
[s personInstanceMethod];

運(yùn)行結(jié)果:

不停的調(diào)用   NSLog(@"do something"); 這一句

這里產(chǎn)生了遞歸豺谈,原因是交換不完全

步驟解析:

1贡这、交換前:


交換前

2茬末、執(zhí)行class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));

執(zhí)行添加方法后結(jié)構(gòu)

3、執(zhí)行 class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

這里需要注意的是·將 swizzleSEL (也就是圖中的5) 對(duì)應(yīng)的 swizzleIMP (圖中的6) 替換成 一個(gè)nil盖矫,這里需要注意的是丽惭,從底層源碼中看:
class_replaceMethod —>
addMethod —>
_method_setImplementation

如果reply一個(gè)nil IMP,那么是不會(huì)執(zhí)行的辈双,所以最終指向結(jié)構(gòu)還是2圖的結(jié)構(gòu)

static IMP 
_method_setImplementation(Class cls, method_t *m, IMP imp)
{
    runtimeLock.assertLocked();

    if (!m) return nil;
    if (!imp) return nil;

    IMP old = m->imp;
    m->imp = imp;
....
    return old;
}

執(zhí)行過(guò)程:
結(jié)合圖2:
調(diào)用9 -》 執(zhí)行 10责掏;
10 調(diào)用 5 -》 執(zhí)行 6
6 調(diào)用5 -》 執(zhí)行6
....
所以發(fā)生了遞歸

錯(cuò)誤原因:查看替換后的結(jié)構(gòu)


按照之前的方法替換后的結(jié)構(gòu)

知道了原因,針對(duì)這個(gè)問(wèn)題來(lái)解決:
解決思路就是:判斷有沒(méi)有IMP湃望,如果沒(méi)有IMP换衬,就添加一個(gè)默認(rèn)的IMP(在這里就是 - (void)readBook {})
代碼如下:

+ (void)je_methodSwizzlingWithClass:(Class)cls
                             oriSEL:(SEL)oriSEL
                        swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls){
        NSLog(@"傳入的交換類(lèi)不能為空");
        return;
    }
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    if (!oriMethod) {
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }

    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
}

按照最終的方案替換后的結(jié)構(gòu)


最終交換后的結(jié)構(gòu)

類(lèi)方法交換

上面說(shuō)了這么多,都是針對(duì)對(duì)象方法(實(shí)例方法)來(lái)講的证芭,那么如果想交換類(lèi)方法要怎么處理瞳浦?
如果搞明白了 對(duì)象 -> 類(lèi) —> 元類(lèi) —> 根源類(lèi) 的關(guān)系,并且知道實(shí)例方法和類(lèi)方法的存儲(chǔ)位置废士,這個(gè)問(wèn)題就很容易解決叫潦。

知識(shí)點(diǎn)補(bǔ)充:
1、類(lèi)的isa 指向 元類(lèi) 官硝,元類(lèi)的isa指向 根源類(lèi) 參照之前的文章
2矗蕊、實(shí)例方法存儲(chǔ)在類(lèi)對(duì)象中,類(lèi)方法存儲(chǔ)在元類(lèi)對(duì)象中

解決上面的問(wèn)題氢架,只需要在交換的時(shí)候 傳入的類(lèi) 傳入元類(lèi)就好了傻咖。
代碼如下:

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = object_getClass(self); 
        [JERuntimeTool je_methodSwizzlingWithClass:class oriSEL:@selector(personMethod) swizzledSEL:@selector(je_readBook)];
    });
}

method swizzling 總結(jié)和注意事項(xiàng)

一、方法交換的調(diào)用時(shí)機(jī):

在+ (void)load方法中調(diào)用岖研。
為什么:
1)卿操、自動(dòng)調(diào)用 2)、調(diào)用的早缎玫。 load方法在app啟動(dòng)的時(shí)候硬纤,就由系統(tǒng)自動(dòng)調(diào)用了解滓。
2赃磨、保證交換的唯一性(需要用單例的形式)

二、load方法的加載順序

1)洼裤、有繼承關(guān)系的類(lèi) 先加載父類(lèi)(不包含其拓展)再加載子類(lèi)
2)邻辉、不同類(lèi)之間的load是按照編譯順序來(lái)決定的(即使是有繼承關(guān)系的類(lèi) 他們的拓展之間也是按照編譯順序來(lái)的)
3)、推展類(lèi)的調(diào)用是在所有的類(lèi)加載完成之后,(可參照源碼 中的map_images方法: 類(lèi) > protocol > category)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末值骇,一起剝皮案震驚了整個(gè)濱河市莹菱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吱瘩,老刑警劉巖道伟,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異使碾,居然都是意外死亡蜜徽,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)票摇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拘鞋,“玉大人,你說(shuō)我怎么就攤上這事矢门∨枭” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵祟剔,是天一觀的道長(zhǎng)隔躲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)物延,這世上最難降的妖魔是什么蹭越? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮教届,結(jié)果婚禮上响鹃,老公的妹妹穿的比我還像新娘。我一直安慰自己案训,他們只是感情好买置,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著强霎,像睡著了一般忿项。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上城舞,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天轩触,我揣著相機(jī)與錄音,去河邊找鬼家夺。 笑死脱柱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拉馋。 我是一名探鬼主播榨为,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼惨好,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了随闺?” 一聲冷哼從身側(cè)響起日川,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎矩乐,沒(méi)想到半個(gè)月后龄句,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡散罕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年撒璧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笨使。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卿樱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出硫椰,到底是詐尸還是另有隱情繁调,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布靶草,位于F島的核電站蹄胰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏奕翔。R本人自食惡果不足惜裕寨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望派继。 院中可真熱鬧宾袜,春花似錦、人聲如沸驾窟。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)绅络。三九已至月培,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恩急,已是汗流浹背杉畜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衷恭,地道東北人此叠。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像匾荆,于是被迫代替她去往敵國(guó)和親拌蜘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杆烁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 763評(píng)論 0 1
  • 我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言牙丽,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢简卧?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,195評(píng)論 0 7
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí),它使得 Objective-C 如虎添翼烤芦,具備了靈活的...
    lylaut閱讀 800評(píng)論 0 4
  • Method Swizzling參考資料 1.用到的運(yùn)行時(shí)基礎(chǔ)知識(shí)介紹 SEL : 方法選擇器,SEL是函數(shù)ob...
    shannoon閱讀 1,366評(píng)論 0 7
  • 文中的實(shí)驗(yàn)代碼我放在了這個(gè)項(xiàng)目中举娩。 以下內(nèi)容是我通過(guò)整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 923評(píng)論 0 6