Runtime中Swizzle時你可能沒注意到的問題

前言

聽說最近女生都喜歡找程序員做男朋友,說是我們程序員一般都穩(wěn)重霞揉,專一旬薯,工資高,如果你長得還挺帥那估計就搶手貨了适秩。本來我計劃今天出去跑步的绊序,但是下雨沒去成,因為如果我再減掉15斤體重秽荞,那我就具備上面長得帥優(yōu)秀條件了骤公,估計我離我女神就越來越近了。

霸氣女神.jpg

上面我減掉15斤就能帥了的話扬跋,如果你信了的話阶捆,我就感謝下你對我的肯定,因為我自己都不信,我怎么可能那么帥呢洒试。但是有女生說喜歡找我們程序員卻不是謠言倍奢,因為我聽我女友說的,我女友聽朋友說的垒棋。是不是有點小激動!還沒有女友的伙伴動起來卒煞,加把勁,代碼敲起來捕犬,程序跑起來跷坝。

激動的我和我的小伙伴.gif

正題:

開發(fā)中酵镜,我們有時會使用runtime進行swizzle方法(如果不知道swizzle請看這里鏈接)碉碉。多數(shù)時候我們會這樣寫,swizzle第一種寫法:

    Method originalMethod = class_getInstanceMethod(aClass, originalSel);
    Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
    method_exchangeImplementations(originalMethod, swizzleMethod);

或者是下面這種方式淮韭,swizzle第二種寫法:

   Method originalMethod = class_getInstanceMethod(aClass, originalSel);
    Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
    BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
    if (didAddMethod) {
        class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, swizzleMethod);
    }

網(wǎng)上資料查找時發(fā)現(xiàn)第二種方式網(wǎng)上有稱呼為最佳的swizzle方式垢粮,具體可見資料(最佳實踐相關(guān)資料).
上面兩種方式具體哪種好呢,我下不定論靠粪,因為在使用過程中蜡吧,發(fā)現(xiàn)他們各自都有自己的問題(將在下面指出),具體還得看自己需求覺得哪種好占键,下面將解釋這兩種方式的一些問題昔善,本文代碼見代碼地址
先說說第一種情況(當swizzle一個自身沒有實現(xiàn)而父類實現(xiàn)了的方法時)下兩種方式比較:
首先我們建立兩個類:father類以及繼承fahter類的son類

@interface Father : NSObject
-(void)work;
@end
@implementation Father
-(void)work{
    NSLog(@"父親得賺錢養(yǎng)家");
}
@end

@interface Son : Father
@end

@implementation Son

@end

然后新建Son分類:

@implementation Son (Swizzle)
BOOL simple_Swizzle(Class aClass, SEL originalSel,SEL swizzleSel){
    
    Method originalMethod = class_getInstanceMethod(aClass, originalSel);
    Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
    method_exchangeImplementations(originalMethod, swizzleMethod);
    
    return YES;
}
BOOL best_Swizzle(Class aClass, SEL originalSel,SEL swizzleSel){
    
    Method originalMethod = class_getInstanceMethod(aClass, originalSel);
    Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
    BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
    if (didAddMethod) {
        class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, swizzleMethod);
    }
    
    return YES;
}
@end

這里我們將第一種方式稱為simple_Swizzle畔乙,第二種方式為best_Swizzle君仆。
情況一:子類里swizzle一個自身沒有實現(xiàn)而父類實現(xiàn)了的方法。
我們在分類中添加一個自己的work方法準備swizzle

@implementation Son (Swizzle)
-(void)son_work{
    [self son_work];
    NSLog(@"son分類里的son_work");
}

@end

然后再load方法中進行替換,我們先使用simple_Swizzle牲距。

+(void)load{
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
             simple_Swizzle([self class], NSSelectorFromString(@"work"), @selector(son_work));
    };
    
}

嗯返咱,好了,運行程序后,調(diào)用如下代碼

    Son *son = [Son new];
    [son work];
    

控制臺打印如下:

2017-06-18 16:11:20.443302 runtime學習[45823:16276777] 父親得賺錢養(yǎng)家
2017-06-18 16:11:20.448797 runtime學習[45823:16276777] son分類里的son_work

也許這里你已經(jīng)發(fā)現(xiàn)問題牍鞠,如果沒有我們繼續(xù)咖摹,再調(diào)用下面代碼:

    Father *aFather = [Father new];
    [aFather work];

程序崩潰了,控制臺輸出:

[Father son_work]: unrecognized selector sent to instance 0x1700162f0'

what?(黑人問號难述?)萤晴,什么情況!胁后![aFather work]里調(diào)用了son_work硫眯。
原來,剛剛的simple_Swizzle將子類的son_work的IMP替換給類父類的work方法择同,導致父類方法work里有[self son_work];而父類并沒有這樣的方法所以over了两入。
具體為什么會替換掉父類的方法,是因為class_getInstanceMethod方法在找方法時會從自己類到父類到根類一直查找下去敲才,一直到根類為止裹纳,所以上面在查找work方法時就找到father類里了择葡。這就是simple_Swizzle的一個問題。
現(xiàn)在我們使用best_Swizzle來進行替換剃氧。

  best_Swizzle([self class], NSSelectorFromString(@"work"), @selector(son_work));

然后

    Son *son = [Son new];
    [son work];
    

控制臺打印如下:

2017-06-18 16:27:43.491944 runtime學習[45833:16282207] 父親得賺錢養(yǎng)家
2017-06-18 16:27:43.493439 runtime學習[45833:16282207] son分類里的son_work

沒問題敏储,接著:

    Father *aFather = [Father new];
    [aFather work];

控制臺打印如下:

2017-06-18 16:30:39.622756 runtime學習[45836:16282868] 父親得賺錢養(yǎng)家

可以看到這時父類沒有調(diào)用son_work。因為best_Swizzle在進行swizzle時會先嘗試給自己添加work方法方法實現(xiàn)

    BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));

如果son類沒有實現(xiàn)work方式時朋鞍,class_addMethod就會成功添加一個新的屬于son自己的work方法已添,同時將本來要swizzle的方法的實現(xiàn)直接復制進work里,然后再將父類的IMP給swizzle。如果son已經(jīng)已經(jīng)實現(xiàn)了則會添加失敗滥酥,直接進行swizzle更舞,邏輯為下面代碼。

    if (didAddMethod) {
        class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, swizzleMethod);
    }

這時simple_Swizzle會有問題坎吻,best_Swizzle沒有問題缆蝉。
下面我們來說另外一種情況(swizzle一個子類到父類到根類都沒有實現(xiàn)過的方法):
我們給son分類添加一個son_Cry方法:

-(void)son_Cry{
    NSLog(@"我是son分類,WZ_Cry方法");
    [self son_Cry];
}

同時聲明一個沒有實現(xiàn)的方法cry;

@interface Son : Father

-(void)cry;
@end

然后先用simple_Swizzle替換:

simple_Swizzle([self class], @selector(cry), @selector(son_Cry));

接著調(diào)用cry方法:

Son *son = [Son new];
[son cry];

控制臺打印:

[Son cry]: unrecognized selector sent to instance 0x170018900'

嗯,調(diào)用失敗瘦真,正常因為都沒有實現(xiàn)如何swizzle刊头。然后用我們用回自動添加一個實現(xiàn)的best_Swizzle方式,控制臺打印如下:

2017-06-18 16:59:33.815223 runtime學習[45845:16289311] 我是son分類,WZ_Cry方法
2017-06-18 16:59:33.815243 runtime學習[45845:16289311] 我是son分類,WZ_Cry方法
2017-06-18 16:59:33.815319 runtime學習[45845:16289311] 我是son分類,WZ_Cry方法
...此處省略無數(shù)次上面的log打印

再一次(what诸尽?黑人問號?),程序死循環(huán)了...
問題出在這兩行代碼:

//因為cry方法沒有實現(xiàn)過原杂,class_getInstanceMethod無法找到該方法,所以originalMethod為nil您机。
 Method originalMethod = class_getInstanceMethod(aClass, originalSel);
 
 //當originalMethod為nil時這里class_replaceMethod將不做替換穿肄,所以swizzleSel方法里的實現(xiàn)還是自己原來的實現(xiàn)。
    class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));

所以最終導致在下面方法里重復調(diào)用自己往产。

-(void)son_Cry{
    NSLog(@"我是son分類,WZ_Cry方法");
    [self son_Cry];
}

這里就是best_Swizzle的一個問題被碗,當然這個問題很少出現(xiàn),因為我們一般swizzle時都是swizzle一個已知的方法仿村,所以一般都有方法實現(xiàn)锐朴。
但是也不是不會出現(xiàn)這種問題,這里我在一次進行swizzle一個類實現(xiàn)的協(xié)議方法時出現(xiàn)了蔼囊,比如AppDelegate類里的<UIApplicationDelegate>協(xié)議焚志,我想在某個代理回調(diào)里插入一段自己的邏輯,又不想對已有的項目里直接加入畏鼓,我想它時可以直接拿到另外一個項目也可以直接用的酱酬,所以我建了一個分類swizzle協(xié)議方法,如果我直接swizzle一個沒有實現(xiàn)過的協(xié)議方法云矫,就會出現(xiàn)死循環(huán)膳沽。
具體可見demo
里的解決方法是在originalMethod為nil時,替換后將swizzleSel復制一個不做任何事的空實現(xiàn),代碼如下:

    if (!originalMethod)
    {
        class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
        method_setImplementation(swizzleMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }

喝口水 總結(jié):

對于simple_Swizzle和best_Swizzle的選擇沒有具體要求,大家結(jié)合自己的場景使用挑社,我一般直接用simple_Swizzle陨界,因為我知道這個方法一定有,所以能簡單就粗暴吧痛阻。

如果瘦下來的我.jpeg

對于runtime菌瘪,大家使用起來還是的謹慎些,一不小心就會入坑阱当。

如果喜歡點個喜歡吧俏扩,因為你的喜歡就是你的喜歡,哈哈哈哈弊添。

一些資料

這里有6篇文章录淡,點擊吧,Go!Go!GO!

g

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末表箭,一起剝皮案震驚了整個濱河市赁咙,隨后出現(xiàn)的幾起案子钮莲,更是在濱河造成了極大的恐慌免钻,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崔拥,死亡現(xiàn)場離奇詭異极舔,居然都是意外死亡,警方通過查閱死者的電腦和手機链瓦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門拆魏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慈俯,你說我怎么就攤上這事渤刃。” “怎么了贴膘?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵卖子,是天一觀的道長。 經(jīng)常有香客問我刑峡,道長洋闽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任突梦,我火速辦了婚禮诫舅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宫患。我一直安慰自己刊懈,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著虚汛,像睡著了一般当宴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泽疆,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天户矢,我揣著相機與錄音,去河邊找鬼殉疼。 笑死梯浪,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的瓢娜。 我是一名探鬼主播挂洛,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼眠砾!你這毒婦竟也來了虏劲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤褒颈,失蹤者是張志新(化名)和其女友劉穎柒巫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谷丸,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡堡掏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了刨疼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泉唁。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖揩慕,靈堂內(nèi)的尸體忽然破棺而出亭畜,到底是詐尸還是另有隱情,我是刑警寧澤迎卤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布拴鸵,位于F島的核電站,受9級特大地震影響止吐,放射性物質(zhì)發(fā)生泄漏宝踪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一碍扔、第九天 我趴在偏房一處隱蔽的房頂上張望瘩燥。 院中可真熱鬧,春花似錦不同、人聲如沸厉膀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽服鹅。三九已至凳兵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間企软,已是汗流浹背庐扫。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留仗哨,地道東北人形庭。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像厌漂,于是被迫代替她去往敵國和親萨醒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

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

  • 黑魔法 Method_Swizzling 原理: Method_Swizzling是發(fā)生在運行時的苇倡,主要用于在運行...
    dullgrass閱讀 3,981評論 2 6
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉富纸,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,681評論 0 9
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)旨椒,斷路器晓褪,智...
    卡卡羅2017閱讀 134,600評論 18 139
  • 一個類想要成為javabean有以下幾個要求: 提供一個默認的無參構(gòu)造函數(shù)。 需要被序列化并且實現(xiàn)了Seriali...
    小小機器人閱讀 342評論 0 1
  • 《親愛的(一)》 親愛的钩乍, 你像一顆塵土辞州,飛飛揚揚的怔锌, 你要去哪里寥粹? 親愛的, 你的眼睛里到處布滿血絲和恐懼埃元, 是...
    安蘇蘇安閱讀 229評論 0 3