前言
聽說最近女生都喜歡找程序員做男朋友,說是我們程序員一般都穩(wěn)重霞揉,專一旬薯,工資高,如果你長得還挺帥那估計就搶手貨了适秩。本來我計劃今天出去跑步的绊序,但是下雨沒去成,因為如果我再減掉15斤體重秽荞,那我就具備上面長得帥優(yōu)秀條件了骤公,估計我離我女神就越來越近了。
上面我減掉15斤就能帥了的話扬跋,如果你信了的話阶捆,我就感謝下你對我的肯定,因為我自己都不信,我怎么可能那么帥呢洒试。但是有女生說喜歡找我們程序員卻不是謠言倍奢,因為我聽我女友說的,我女友聽朋友說的垒棋。是不是有點小激動!還沒有女友的伙伴動起來卒煞,加把勁,代碼敲起來捕犬,程序跑起來跷坝。
正題:
開發(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陨界,因為我知道這個方法一定有,所以能簡單就粗暴吧痛阻。
對于runtime菌瘪,大家使用起來還是的謹慎些,一不小心就會入坑阱当。
如果喜歡點個喜歡吧俏扩,因為你的喜歡就是你的喜歡,哈哈哈哈弊添。