coding 的演示功能不讓用,原來搭建的博客訪問不了了。索性將全部博客遷移到簡書,這篇是舊文章,歡迎大家以后來簡書看我的博客
上一篇博客中我們簡單介紹了Method Swizzling,看過上一篇文章也許大家會覺得Method Swizzling is so easy
,不過事情沒那么簡單。有很多細節任然需要我們注意!!!
細節
在ARC之前,我們經常備受內存管理的困擾,有時候一些對象莫名其妙就被釋放了,都不知道在哪兒釋放的。在任何對象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:的實現 */
class_replaceMethod(class,
overrideSel,
method_getImplementation(originMethod),
method_getTypeEncoding(originMethod));
}else{
/** case2:NSMutableDictionary中有-setObject:forKey:的實現 */
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下編譯器會報錯
例子有些簡單和幼稚,現實中可能沒有這樣的需求,不過這都不是重點,重點是……
1. +load
VS +initialize
有沒有人想過為什么Swizzling的代碼要放在+load中?why?
=> 因為Swizzling的代碼必須要在方法執行之前,否則方法都執行了,再Swizzling就沒有意義了,而+load方法是main函數之前調用的(class添加runtime中的時候調用),所以可以保證在方法執行前Swizzling……
+initialize方法也可以保證在方法執行前調用,那是否可以將Swizzling的代碼放在這個里面?
=> 放在+initialize里面大多數時候是不會有問題的,不過這樣做會有風險。我們知道,+initialize方法是在Class接到第一個消息之前執行,也就是說,多個有繼承關系的類,他們的+initialize方法執行的順序取決于具體的代碼,哪個類先接收到第一個消息,哪個類的+initialize方法就先執行。如果這幾個類都對同一個Method執行了Swizzling,這就會導致他們行為的不確定性,我們不知道哪個Swizzling先執行,從而可能會產生隱藏的難以發現的bug。而+load執行的順序是確定的。父類的+load先執行,之后才執行子類
2. dispatch_once
顯而易見,如果多個線程同時執行同一段Swizzling的代碼,有可能造成混亂,產生我們不希望的結果,所以一般Swizzling的代碼都需要放在dispatch_once中,不過由于系統保證了+load方法不會同時執行多次,所以在+load中不加dispatch_once也影響不大,不過為了養成良好的代碼系統,加上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的關系如圖所示,下面2點我需要解釋一下:
- Instance的class指針指向Class,Class的class指針指向MetalClass。簡單來說可以理解為MetalClass的實例為Class,Class的實例為真正的實例Instance。
- MetalClass的結構與Class的結構完全相同,他們的區別只是Class中存放實例方法,MetalClass中存放類方法。
所以首先,object_getClass()方法傳入一個實例對象,返回實例對象的Class。由1可知,傳入class,返回MetalClass。
由于Class中只存放實例方法,所以要對類方法進行Swizzling必須要在MetalClass上進行,并且MetalClass和Class的結構完全相同,所以只需要將原來的Class替換成metalClass即可對ClassMethod進行Swizzling。
Danger
大家可能都知道Method Swizzling是有風險的,但是具體風險在哪里,可能大家就不太明確了。我查找了一些資料,收集了一些Swizzling存在風險的地方,如果大家還發現其他的風險,請聯系我。
1.Refused by AppStore
危險系數:★★★★★
遭遇概率:★☆☆☆☆
曾經出現過由于Swizzling系統API而被AppStore拒絕的事情,不過我在網上查了很長時間,這個事件僅發生了一次,并且后面的人做同樣的事并沒有遭到拒絕,這個應該也和審核的人有關。一般情況下應該是不會被拒的,所以這個危險系數極高,但是遭遇概率也非常低。事件的詳細情況看這里
2.Class Cluster
危險系數:★★★★☆
遭遇概率:★★☆☆☆
類族的概念在上一篇博客里說起過。簡單的說,類族就是表面上我們似乎只是在用一個類,例如NSString(NSNumber,NSArray,NSDictionary等類似),實際我們使用的是他們的子類,如:__NSCFConstantString,NSPathStore2,NSBigMutableString等,由于這些子類并不公開,我們不知道到底有多少子類,以后還會不會增加子類,從而Swizzling的時候無法對所有子類覆蓋,導致可能有的情況下使用NSString的方法是Swizzling過的,有的情況下調用的是未Swizzling的方法。所以對于這種情況,建議放棄使用Method Swizzling。
3. Swizzling changes the method's arguments
危險系數:★★☆☆☆
遭遇概率:★★★★☆
Swizzling改變了方法的參數。我們知道,當我們調用一個方法時[obj test],系統會將其轉化為消息發送objc_msgSend(obj,@selector(test)),并將obj和SEL傳送到方法中,在方法中,可以通過_cmd
獲取傳入的SEL,通過剛剛的傳統Swizzling方法Swizzling過后,傳入原函數的SEL改變了,若原函數中使用了_cmd
,就有可能發生錯誤。幸好,這個風險是可以避免的,通過以下的修改即可避免這個風險。
- (void)swzzling_dealloc
{
printf("我在這里被釋放了");
// [self swzzling_dealloc];
IMP originImp = class_getMethodImplementation([self class], @selector(swzzling_dealloc));
originImp(self,_cmd);
}
我們直接調用IMP,將正確的_cmd
傳入其中即可。
直接調用IMP的寫法有些粗魯,過幾天有時間可以研究一下將其封裝成一個方法,再過來更新
更新:由于IMP傳入的參數是可變長參數,因此封裝的方法傳入的參數必須也為可變長參數,然而目前無法做到將可變長參數專遞給另一函數,所以這個地方暫時無法封裝成一個方法。如果你有什么其他辦法可以做到,請聯系我
4.others
這里還列出了一些其他的風險,念茜大神在這里對他進行了翻譯,我就不在這里贅述了。感興趣的朋友可以看看,里面還提供了另外一種Method Swizzling的方法,同樣也可以解決3中這個問題。
參考
Method Swizzling in codeproject