【遷移】MethodSwizzling(二)

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。

Class 關系圖
Class 關系圖

Instance,Class,MetalClass的關系如圖所示,下面2點我需要解釋一下:

  1. Instance的class指針指向Class,Class的class指針指向MetalClass。簡單來說可以理解為MetalClass的實例為Class,Class的實例為真正的實例Instance。
  2. 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 nshipster

Method Swizzling in codeproject

What are the Dangers of Method Swizzling in Objective C?

Objective-C的hook方案(一): Method Swizzling

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,746評論 0 9
  • 前言 到了今天終于要"出院"了,要總結一下住院幾天的收獲,談談Runtime到底能為我們開發帶來些什么好處。當然它...
    一縷殤流化隱半邊冰霜閱讀 23,397評論 56 317
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,781評論 18 139
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,578評論 33 466
  • 文中的實驗代碼我放在了這個項目中。 以下內容是我通過整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 936評論 0 6