OC運(yùn)行時(shí)機(jī)制Runtime(四):嘗試使用黑魔法 Method Swizzling

Runtime最全總結(jié)

本系列詳細(xì)講解Runtime知識(shí)點(diǎn),由于運(yùn)行時(shí)的內(nèi)容較多,所以將內(nèi)容拆分成以下幾個(gè)方面,可以自行選擇想要查看的部分

本文主要分析Runtime黑魔法——Method Swizzling矿咕,接前兩篇文章詳細(xì)分析一下一些細(xì)節(jié)內(nèi)容。

Method Swizzling

通過(guò)前文分析Category狼钮,我們了解了OC在運(yùn)行時(shí)可以動(dòng)態(tài)添加方法碳柱,這種方式比起繼承的方式要輕量級(jí)許多,但是這種方式更適合新增某個(gè)方法熬芜,如果我們希望在原有基礎(chǔ)的方法上添加一定功能莲镣,那么繼承和分類的方式還是不大適合,這里我們會(huì)使用一個(gè)新的方法Method Swizzling涎拉。

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

這是method結(jié)構(gòu)體的內(nèi)容瑞侮,主要SEL類型的方法名稱以及IMP類型的函數(shù)指針,也就是方法的具體實(shí)現(xiàn)鼓拧。那么當(dāng)我們修改IMP指針的指向時(shí)半火,就相當(dāng)于修改了該方法的具體實(shí)現(xiàn)。

下面我們簡(jiǎn)單測(cè)試一下季俩。

Person * person = [Person new];
Method method1 = class_getInstanceMethod([Person class], @selector(speakAction));
Method method2 = class_getInstanceMethod([Person class], @selector(runAction));

method_exchangeImplementations(method1, method2);
    
[person speakAction];
[person runAction];

首先我們獲取兩個(gè)方法钮糖,使用method_exchangeImplementations這個(gè)函數(shù)將兩個(gè)方法的IMP指針的指向做交換,然后我們執(zhí)行這兩個(gè)方法酌住,看一下方法內(nèi)的打印結(jié)果藐鹤。

2019-04-16 14:15:31.014450+0800 Run
2019-04-16 14:15:31.014705+0800 Speak

這里能看到runAction方法首先被log出來(lái),說(shuō)明了兩個(gè)方法已經(jīng)做到了交換赂韵,我們找到函數(shù)的具體實(shí)現(xiàn)來(lái)徹底印證這個(gè)猜想。

void method_exchangeImplementations(Method m1_gen, Method m2_gen)
{
    method_t *m1 = newmethod(m1_gen);
    method_t *m2 = newmethod(m2_gen);
    if (!m1  ||  !m2) return;

    rwlock_write(&runtimeLock);

    if (ignoreSelector(m1->name)  ||  ignoreSelector(m2->name)) {
        // Ignored methods stay ignored. Now they're both ignored.
        m1->imp = (IMP)&_objc_ignored_method;
        m2->imp = (IMP)&_objc_ignored_method;
        rwlock_unlock_write(&runtimeLock);
        return;
    }

    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;

    if (vtable_containsSelector(m1->name)  ||  
        vtable_containsSelector(m2->name)) 
    {
        // Don't know the class - will be slow if vtables are affected
        // fixme build list of classes whose Methods are known externally?
        flushVtables(NULL);
    }

    // fixme catch NSObject changing to custom RR
    // cls->setCustomRR();

    // fixme update monomorphism if necessary

    rwlock_unlock_write(&runtimeLock);
}

忽略嚴(yán)格處理部分挠蛉,可以看到m1的imp指針m2的imp指針確實(shí)做到了交換祭示,以此實(shí)現(xiàn)交換了方法的具體實(shí)現(xiàn),下面用一個(gè)圖來(lái)表示這個(gè)操作谴古。

Method Swizzling具體實(shí)現(xiàn)

看得出Method Swizzling的原理并不很難理解质涛,使用起來(lái)也很便利,我們來(lái)想一個(gè)簡(jiǎn)單的需求:項(xiàng)目中很多時(shí)候我們可能用到ImageView展示圖片掰担,如果圖片展示不出我們不確定是否原因在于url字段問(wèn)題汇陆,那我們可以在添加url方法加上一段log,判斷字符串是否為空带饱,demo中我們使用本地圖片方式進(jìn)行測(cè)試毡代。

#import "UIImage+Detection.h"

@implementation UIImage (Detection)

+ (void)load {
    Method originalMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
    Method swappedMethod = class_getClassMethod([UIImage class], @selector(hy_imageNamed:));
    method_exchangeImplementations(originalMethod, swappedMethod);
}

+ (UIImage *)hy_imageNamed:(NSString *)nameString {
    if ([nameString isEqualToString:@""]) {
        NSLog(@"missing image name");
    }
    return [UIImage hy_imageNamed:nameString];
}
@end

我們?yōu)閁IImage創(chuàng)建一個(gè)分類阅羹,創(chuàng)建一個(gè) hy_imageNamed的方法,方法內(nèi)對(duì)imageName字符串進(jìn)行判斷教寂,如果字符串為空我們進(jìn)行一個(gè)日志打印捏鱼,load方法中我們把系統(tǒng)的imageNamed:方法與我們的方法進(jìn)行交換,這樣一來(lái)酪耕,外部調(diào)用imageNamed方法時(shí)导梆,實(shí)際是調(diào)用了我們的hy_imageNamed:方法。
返回值雖然調(diào)用的是 hy_imageNamed 方法迂烁,但是由于有方法交換的原因所以這里是調(diào)用了原生 imageNamed 方法看尼,并沒(méi)有造成遞歸調(diào)用。

UIImage * image = [UIImage imageNamed:@""];

這樣一來(lái)控制臺(tái)會(huì)打印找不到圖片的日志信息盟步,滿足了我們的簡(jiǎn)單需求藏斩,項(xiàng)目中我們可以自由發(fā)揮,不過(guò)也不能濫用址芯,以防給后期代碼維護(hù)造成困難灾茁。

總結(jié)

方法交換的實(shí)質(zhì)就是通過(guò)交換IMP函數(shù)指針的指向,來(lái)交換方法的具體實(shí)現(xiàn)谷炸,我們可以將Method Swizzling這段代碼寫到任何一處北专,但是只有執(zhí)行了這段代碼之后才可以實(shí)現(xiàn)交換。

最后

Runtime相關(guān)還有很多知識(shí)點(diǎn)旬陡,例如kvo的原理拓颓,weak的實(shí)現(xiàn)等等,但是作者文筆有限描孟,深層問(wèn)題還在探索中驶睦,希望可以在源碼角度解析而不是很淺層的分析,歡迎大家一起討論匿醒,更歡迎大牛們給一些意見(jiàn)或建議场航,如果覺(jué)得本文對(duì)您有些作用,請(qǐng)?jiān)谙路近c(diǎn)個(gè)贊再走哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末廉羔,一起剝皮案震驚了整個(gè)濱河市溉痢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌憋他,老刑警劉巖孩饼,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異竹挡,居然都是意外死亡镀娶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門揪罕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)梯码,“玉大人宝泵,你說(shuō)我怎么就攤上這事∪绦” “怎么了鲁猩?”我有些...
    開(kāi)封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)罢坝。 經(jīng)常有香客問(wèn)我廓握,道長(zhǎng),這世上最難降的妖魔是什么嘁酿? 我笑而不...
    開(kāi)封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任隙券,我火速辦了婚禮,結(jié)果婚禮上闹司,老公的妹妹穿的比我還像新娘娱仔。我一直安慰自己,他們只是感情好游桩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布牲迫。 她就那樣靜靜地躺著,像睡著了一般借卧。 火紅的嫁衣襯著肌膚如雪盹憎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天铐刘,我揣著相機(jī)與錄音陪每,去河邊找鬼。 笑死镰吵,一個(gè)胖子當(dāng)著我的面吹牛檩禾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疤祭,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼盼产,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了勺馆?” 一聲冷哼從身側(cè)響起辆飘,我...
    開(kāi)封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谓传,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芹关,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡续挟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侥衬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诗祸。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跑芳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出直颅,到底是詐尸還是另有隱情博个,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布功偿,位于F島的核電站盆佣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏械荷。R本人自食惡果不足惜共耍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吨瞎。 院中可真熱鬧痹兜,春花似錦、人聲如沸颤诀。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)崖叫。三九已至遗淳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間归露,已是汗流浹背洲脂。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剧包,地道東北人恐锦。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像疆液,于是被迫代替她去往敵國(guó)和親一铅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355