class_addMethod實(shí)解

閑來(lái)無(wú)事弹灭,整理了一下runtime的知識(shí),發(fā)現(xiàn)方法交換里面有個(gè)不明白的點(diǎn):class_addMethod 這個(gè)方法的返回值到底怎么解釋列吼?因?yàn)闇y(cè)試了類方法和實(shí)例方法之后沪编,發(fā)現(xiàn)返回的結(jié)果不一樣,就很迷惑坎匿,在網(wǎng)上搜出來(lái)的結(jié)果越看越糊涂盾剩。。替蔬。后來(lái)第二天一早恍然大悟告私,原來(lái)是原理沒(méi)搞清楚。

1承桥、問(wèn)題展示
先來(lái)碼一下我走過(guò)的坑:
(1)替換 UIImage 類的 init 方法

#import "UIImage+Swizzle.h"
#import <objc/message.h>

@implementation UIImage (Swizzle)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleInstanceSel:@selector(init) withNewSel:@selector(qq_init)];
    });
}

+ (void)swizzleInstanceSel:(SEL)oldSel withNewSel:(SEL)newSel {
    Class class = self.class;
    Method oldM = class_getInstanceMethod(class, oldSel);
    Method newM = class_getInstanceMethod(class, newSel);
    BOOL didAdd = class_addMethod(class, oldSel, method_getImplementation(newM), method_getTypeEncoding(newM));
    if (didAdd) {
        NSLog(@"swizzleInstanceSel * didAdd");
        class_replaceMethod(class, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
    }
    else {
        NSLog(@"swizzleInstanceSel * didn'tAdd ----> exchange!");
        method_exchangeImplementations(oldM, newM);
    }
}

- (instancetype)qq_init {
    NSLog(@"自定義的qq_init方法 - %s", __func__);
    return [self qq_init];
}

@end

然后在 ViewController 類中調(diào)用 UIImage 的 init 方法:UIImage * image = [[UIImage alloc] init];驻粟,結(jié)果控制器的打印如下:

2018-06-15 11:00:37.155152+0800 AddMethodTest[22950:4869406] swizzleInstanceSel * didAdd
2018-06-15 11:00:37.274999+0800 AddMethodTest[22950:4869406] 自定義的qq_init方法 - -[UIImage(Swizzle) qq_init]

從打印中可以看出,class_addMethod 方法的返回值 didAdd 為YES快毛,即調(diào)用了 class_replaceMethod 方法格嗅。調(diào)用 UIImage 的 init 方法,實(shí)際上運(yùn)行的時(shí)候走的是我們自定義的 qq_init 方法唠帝,達(dá)到效果屯掖,然而看下面??

(2)替換 UIImage 類的 imageNamed 方法

#import "UIImage+Swizzle.h"
#import <objc/message.h>

@implementation UIImage (Swizzle)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleClassSel:@selector(imageNamed:) withNewSel:@selector(qq_imageNamed:)];
    });
}

+ (void)swizzleClassSel:(SEL)oldSel withNewSel:(SEL)newSel {
    Class class = self.class;
    Method oldM = class_getClassMethod(class, oldSel);
    Method newM = class_getClassMethod(class, newSel);
    BOOL didAdd = class_addMethod(class, oldSel, method_getImplementation(newM), method_getTypeEncoding(newM));
    if (didAdd) {
        NSLog(@"swizzleClassSel * didAdd");
        class_replaceMethod(class, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
    }
    else {
        NSLog(@"swizzleClassSel * didn'tAdd ----> exchange!");
        method_exchangeImplementations(oldM, newM);
    }
}

+ (UIImage *)qq_imageNamed:(NSString *)name {
    NSLog(@"自定義的imageNamed方法 - %s", __func__);
    return [self qq_imageNamed:name];
}

@end

然后在 ViewController 類中調(diào)用 UIImage 的 imageNamed 方法:UIImage * image = [UIImage imageNamed:@"1"];,結(jié)果控制器的打印如下:

2018-06-15 11:12:13.806120+0800 AddMethodTest[22955:4873159] swizzleClassSel * didAdd

從打印中可以看出襟衰,class_addMethod 方法的返回值 didAdd 為YES贴铜,即調(diào)用了 class_replaceMethod 方法。但是調(diào)用 UIImage 的 imageNamed 方法瀑晒,實(shí)際上運(yùn)行的時(shí)候并沒(méi)有走我們自定義的 qq_imageNamed 方法绍坝,這是個(gè)什么情況?苔悦?轩褐?
我就很費(fèi)解啊玖详!為什么相同的邏輯在同一個(gè)類的同一個(gè)方法中調(diào)用的結(jié)果不一樣把介??

2蟋座、原理分析
百思不得解之后拗踢,我翻了一下系統(tǒng)中 class_addMethod 的方法介紹,

/** 
 * Adds a new method to a class with a given name and implementation.
 * 
 * @param cls The class to which to add a method.
 * @param name A selector that specifies the name of the method being added.
 * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
 * @param types An array of characters that describe the types of the arguments to the method. 
 * 
 * @return YES if the method was added successfully, otherwise NO 
 *  (for example, the class already contains a method implementation with that name).
 *
 * @note class_addMethod will add an override of a superclass's implementation, 
 *  but will not replace an existing implementation in this class. 
 *  To change an existing implementation, use method_setImplementation.
 */
OBJC_EXPORT BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

很明確向臀,如果method添加成功就返回YES巢墅,否則返回NO(比如類中已經(jīng)包含這個(gè)方法實(shí)現(xiàn))。好像忽然就明白了券膀,君纫, 實(shí)例方法的方法列表保存在類中,而類方法的方法列表保存在元類中三娩。這樣好像就說(shuō)得通了庵芭。
其實(shí)這些c方法操作的都是結(jié)構(gòu)體, class_addMethod的第一個(gè)屬性是Class雀监,新增的也是這個(gè)Class里的方法双吆。我這里的 class_addMethod 是給 self.class(class_addMethod方法的第一個(gè)參數(shù)) 添加方法,即給 UIImage 類添加方法会前,而 UIImage 這個(gè)類中保存的都是實(shí)例方法好乐,故此這地方的 class_addMethod 只能在給這個(gè)類添加實(shí)例方法的時(shí)候使用。換句話說(shuō)瓦宜,就是 Class 保存的是 instance 的方法蔚万,所以加的那些方法最終體現(xiàn)在實(shí)例上。

3临庇、解決方法
搞清楚問(wèn)題后就知道了反璃,如果我想替換掉類方法昵慌,就要把第一個(gè)參數(shù)給換成元類的本體,即 objc_getMetaClass(object_getClassName(self)) 就可以了淮蜈。

+ (void)swizzleClassSel:(SEL)oldSel withNewSel:(SEL)newSel {
    Class class = objc_getMetaClass(object_getClassName(self));
    Method oldM = class_getClassMethod(class, oldSel);
    Method newM = class_getClassMethod(class, newSel);
    BOOL didAdd = class_addMethod(class, oldSel, method_getImplementation(newM), method_getTypeEncoding(newM));
    if (didAdd) {
        NSLog(@"swizzleInstanceSel * didAdd");
        class_replaceMethod(class, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
    }
    else {
        NSLog(@"swizzleInstanceSel * didn'tAdd ----> exchange!");
        method_exchangeImplementations(oldM, newM);
    }
}

打印結(jié)果如下:

2018-06-15 11:41:41.630059+0800 AddMethodTest[22958:4876411]  swizzleInstanceSel * didn'tAdd ----> exchange!
2018-06-15 11:41:41.630059+0800 AddMethodTest[22958:4876411]  自定義的imageNamed方法 - +[UIImage(Swizzle) qq_imageNamed:]

這樣就解釋的通了斋攀。


PS:
類和元類的結(jié)構(gòu)體是一致的,所以很多關(guān)系都是可以搬過(guò)來(lái)的梧田。
實(shí)例-類淳蔼,類-元類的區(qū)別在于,實(shí)例是開(kāi)發(fā)new出來(lái)的用于承載業(yè)務(wù)信息的裁眯,類是系統(tǒng)new出來(lái)的鹉梨,用于描述實(shí)例的,表面上的用處是大不一樣的穿稳,但是挖到深層結(jié)構(gòu)就是通用的存皂。

也不知道我理解的對(duì)不對(duì),如果有問(wèn)題司草,歡迎大家指正艰垂,將不勝感激!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末埋虹,一起剝皮案震驚了整個(gè)濱河市猜憎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搔课,老刑警劉巖胰柑,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異爬泥,居然都是意外死亡柬讨,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)袍啡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)踩官,“玉大人,你說(shuō)我怎么就攤上這事境输≌崮担” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵嗅剖,是天一觀的道長(zhǎng)辩越。 經(jīng)常有香客問(wèn)我,道長(zhǎng)信粮,這世上最難降的妖魔是什么黔攒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上督惰,老公的妹妹穿的比我還像新娘不傅。我一直安慰自己,他們只是感情好赏胚,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布蛤签。 她就那樣靜靜地躺著,像睡著了一般栅哀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上称龙,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天留拾,我揣著相機(jī)與錄音,去河邊找鬼鲫尊。 笑死痴柔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的疫向。 我是一名探鬼主播咳蔚,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼搔驼!你這毒婦竟也來(lái)了谈火?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舌涨,失蹤者是張志新(化名)和其女友劉穎糯耍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體囊嘉,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡温技,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扭粱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舵鳞。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖琢蛤,靈堂內(nèi)的尸體忽然破棺而出蜓堕,到底是詐尸還是另有隱情,我是刑警寧澤虐块,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布俩滥,位于F島的核電站,受9級(jí)特大地震影響贺奠,放射性物質(zhì)發(fā)生泄漏霜旧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挂据。 院中可真熱鬧以清,春花似錦、人聲如沸崎逃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)个绍。三九已至勒葱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巴柿,已是汗流浹背凛虽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留广恢,地道東北人凯旋。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像钉迷,于是被迫代替她去往敵國(guó)和親至非。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • 引導(dǎo) 對(duì)于從事 iOS 開(kāi)發(fā)人員來(lái)說(shuō)糠聪,所有的人都會(huì)答出「 Runtime 是運(yùn)行時(shí) 」荒椭,什么情況下用 Runtim...
    Winny_園球閱讀 4,188評(píng)論 3 75
  • 對(duì)于從事 iOS 開(kāi)發(fā)人員來(lái)說(shuō),所有的人都會(huì)答出【runtime 是運(yùn)行時(shí)】什么情況下用runtime?大部分人能...
    夢(mèng)夜繁星閱讀 3,697評(píng)論 7 64
  • 昨天就收到燈塔的格子a5本。之前在選擇本子的時(shí)候夭苗,拋棄了我的舊愛(ài)moleskine信卡,選擇燈塔。理由就是题造,燈塔有頁(yè)碼...
    餓死的麥芽糖閱讀 188評(píng)論 0 0
  • 做一個(gè)查詢頁(yè)面時(shí)碰到的問(wèn)題:在所有條件(一些下拉框傍菇、模糊搜索等等)加載完成后,才能點(diǎn)擊搜索按鈕界赔。所以百度了一番丢习,找...
    duadu閱讀 1,214評(píng)論 0 0
  • 河南洛陽(yáng)市孟津縣常袋中學(xué) 席延飛 見(jiàn)中國(guó)教育報(bào)2012年1月5日 http://paper.jyb.cn/zgjy...
    牽著蝸牛狂奔閱讀 361評(píng)論 2 0