coding 的演示功能不讓用,原來搭建的博客訪問不了了碉京。索性將全部博客遷移到簡書僵芹,這篇是舊文章瀑志,歡迎大家以后來簡書看我的博客
上一篇博客中我們簡單介紹了Method Swizzling,看過上一篇文章也許大家會覺得Method Swizzling is so easy
脖阵,不過事情沒那么簡單皂股。有很多細節(jié)任然需要我們注意!C呜呐!
細節(jié)
在ARC之前,我們經(jīng)常備受內(nèi)存管理的困擾纷铣,有時候一些對象莫名其妙就被釋放了卵史,都不知道在哪兒釋放的。在任何對象dealloc的時候都打印一個log搜立,這樣只要看log就知道在哪兒被釋放了。由于對象的類眾多槐秧,重寫dealloc工作量巨大啄踊,所以我們決定用Swizzling。
上代碼~~~
@implementation NSObject(Swizzling)
void methodSwizzling(Class class,SEL originSel,SEL overrideSel)
{
Method originMethod = class_getInstanceMethod(class, originSel);
Method overrideMethod = class_getInstanceMethod(class, overrideSel);
if (class_addMethod(class,
originSel,
method_getImplementation(overrideMethod),
method_getTypeEncoding(originMethod)))
{
/** case1:NSMutableDictionary中沒有-setObject:forKey:的實現(xiàn) */
class_replaceMethod(class,
overrideSel,
method_getImplementation(originMethod),
method_getTypeEncoding(originMethod));
}else{
/** case2:NSMutableDictionary中有-setObject:forKey:的實現(xiàn) */
method_exchangeImplementations(originMethod, overrideMethod);
}
}
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
methodSwizzling([NSObject class], @selector(dealloc), @selector(swzzling_dealloc));
});
}
- (void)swzzling_dealloc
{
printf("我在這里被釋放了");
[self swzzling_dealloc];
}
@end
例子必須在非ARC下運行刁标,因為@selector(dealloc)在ARC下編譯器會報錯
例子有些簡單和幼稚颠通,現(xiàn)實中可能沒有這樣的需求,不過這都不是重點膀懈,重點是……
1. +load
VS +initialize
有沒有人想過為什么Swizzling的代碼要放在+load中顿锰?why?
=> 因為Swizzling的代碼必須要在方法執(zhí)行之前启搂,否則方法都執(zhí)行了硼控,再Swizzling就沒有意義了,而+load方法是main函數(shù)之前調(diào)用的(class添加runtime中的時候調(diào)用)胳赌,所以可以保證在方法執(zhí)行前Swizzling……
+initialize方法也可以保證在方法執(zhí)行前調(diào)用牢撼,那是否可以將Swizzling的代碼放在這個里面?
=> 放在+initialize里面大多數(shù)時候是不會有問題的疑苫,不過這樣做會有風險熏版。我們知道,+initialize方法是在Class接到第一個消息之前執(zhí)行捍掺,也就是說撼短,多個有繼承關(guān)系的類,他們的+initialize方法執(zhí)行的順序取決于具體的代碼挺勿,哪個類先接收到第一個消息曲横,哪個類的+initialize方法就先執(zhí)行。如果這幾個類都對同一個Method執(zhí)行了Swizzling满钟,這就會導致他們行為的不確定性胜榔,我們不知道哪個Swizzling先執(zhí)行胳喷,從而可能會產(chǎn)生隱藏的難以發(fā)現(xiàn)的bug。而+load執(zhí)行的順序是確定的夭织。父類的+load先執(zhí)行吭露,之后才執(zhí)行子類
2. dispatch_once
顯而易見,如果多個線程同時執(zhí)行同一段Swizzling的代碼尊惰,有可能造成混亂讲竿,產(chǎn)生我們不希望的結(jié)果,所以一般Swizzling的代碼都需要放在dispatch_once中弄屡,不過由于系統(tǒng)保證了+load方法不會同時執(zhí)行多次题禀,所以在+load中不加dispatch_once也影響不大,不過為了養(yǎng)成良好的代碼系統(tǒng)膀捷,加上dispatch_once是最好的
3. Swizzling Class Method
一直以來迈嘹,我們都僅僅是對InstanceMethod(實例方法)進行Swizzling,如果我們需要對Class Method(類方法)進行Swizzling全庸,那該怎么做呢秀仲?
首先我們來看看答案吧:
void classMethodSwizzling(Class class,SEL originSel,SEL overrideSel)
{
Class metalClass = object_getClass(class);
methodSwizzling(metalClass, originSel, overrideSel);
}
我們可以看到,只需要將原來的Class替換成metalClass即可對ClassMethod進行Swizzling壶笼。
but why神僵?
要解釋這個,首先我們需要了解一下Class覆劈。
Instance保礼,Class,MetalClass的關(guān)系如圖所示责语,下面2點我需要解釋一下:
- Instance的class指針指向Class炮障,Class的class指針指向MetalClass。簡單來說可以理解為MetalClass的實例為Class鹦筹,Class的實例為真正的實例Instance铝阐。
- MetalClass的結(jié)構(gòu)與Class的結(jié)構(gòu)完全相同,他們的區(qū)別只是Class中存放實例方法铐拐,MetalClass中存放類方法徘键。
所以首先,object_getClass()方法傳入一個實例對象遍蟋,返回實例對象的Class吹害。由1可知,傳入class虚青,返回MetalClass它呀。
由于Class中只存放實例方法,所以要對類方法進行Swizzling必須要在MetalClass上進行,并且MetalClass和Class的結(jié)構(gòu)完全相同纵穿,所以只需要將原來的Class替換成metalClass即可對ClassMethod進行Swizzling下隧。
Danger
大家可能都知道Method Swizzling是有風險的,但是具體風險在哪里谓媒,可能大家就不太明確了淆院。我查找了一些資料,收集了一些Swizzling存在風險的地方句惯,如果大家還發(fā)現(xiàn)其他的風險土辩,請聯(lián)系我。
1.Refused by AppStore
危險系數(shù):★★★★★
遭遇概率:★☆☆☆☆
曾經(jīng)出現(xiàn)過由于Swizzling系統(tǒng)API而被AppStore拒絕的事情抢野,不過我在網(wǎng)上查了很長時間拷淘,這個事件僅發(fā)生了一次,并且后面的人做同樣的事并沒有遭到拒絕指孤,這個應該也和審核的人有關(guān)启涯。一般情況下應該是不會被拒的,所以這個危險系數(shù)極高邓厕,但是遭遇概率也非常低逝嚎。事件的詳細情況看這里
2.Class Cluster
危險系數(shù):★★★★☆
遭遇概率:★★☆☆☆
類族的概念在上一篇博客里說起過。簡單的說详恼,類族就是表面上我們似乎只是在用一個類,例如NSString(NSNumber,NSArray,NSDictionary等類似)引几,實際我們使用的是他們的子類昧互,如:__NSCFConstantString,NSPathStore2,NSBigMutableString等,由于這些子類并不公開伟桅,我們不知道到底有多少子類敞掘,以后還會不會增加子類,從而Swizzling的時候無法對所有子類覆蓋楣铁,導致可能有的情況下使用NSString的方法是Swizzling過的玖雁,有的情況下調(diào)用的是未Swizzling的方法。所以對于這種情況盖腕,建議放棄使用Method Swizzling赫冬。
3. Swizzling changes the method's arguments
危險系數(shù):★★☆☆☆
遭遇概率:★★★★☆
Swizzling改變了方法的參數(shù)。我們知道溃列,當我們調(diào)用一個方法時[obj test]劲厌,系統(tǒng)會將其轉(zhuǎn)化為消息發(fā)送objc_msgSend(obj,@selector(test)),并將obj和SEL傳送到方法中听隐,在方法中补鼻,可以通過_cmd
獲取傳入的SEL,通過剛剛的傳統(tǒng)Swizzling方法Swizzling過后,傳入原函數(shù)的SEL改變了风范,若原函數(shù)中使用了_cmd
,就有可能發(fā)生錯誤咨跌。幸好,這個風險是可以避免的硼婿,通過以下的修改即可避免這個風險锌半。
- (void)swzzling_dealloc
{
printf("我在這里被釋放了");
// [self swzzling_dealloc];
IMP originImp = class_getMethodImplementation([self class], @selector(swzzling_dealloc));
originImp(self,_cmd);
}
我們直接調(diào)用IMP,將正確的_cmd
傳入其中即可加酵。
直接調(diào)用IMP的寫法有些粗魯拳喻,過幾天有時間可以研究一下將其封裝成一個方法,再過來更新
更新:由于IMP傳入的參數(shù)是可變長參數(shù)猪腕,因此封裝的方法傳入的參數(shù)必須也為可變長參數(shù)冗澈,然而目前無法做到將可變長參數(shù)專遞給另一函數(shù),所以這個地方暫時無法封裝成一個方法陋葡。如果你有什么其他辦法可以做到亚亲,請聯(lián)系我
4.others
這里還列出了一些其他的風險,念茜大神在這里對他進行了翻譯腐缤,我就不在這里贅述了捌归。感興趣的朋友可以看看,里面還提供了另外一種Method Swizzling的方法岭粤,同樣也可以解決3中這個問題惜索。
參考
Method Swizzling in codeproject