【遷移】MethodSwizzling(二)

coding 的演示功能不讓用,原來搭建的博客訪問不了了。索性將全部博客遷移到簡(jiǎn)書,這篇是舊文章,歡迎大家以后來簡(jiǎn)書看我的博客

上一篇博客中我們簡(jiǎn)單介紹了Method Swizzling,看過上一篇文章也許大家會(huì)覺得Method Swizzling is so easy,不過事情沒那么簡(jiǎn)單。有很多細(xì)節(jié)任然需要我們注意!??!

細(xì)節(jié)

在ARC之前,我們經(jīng)常備受內(nèi)存管理的困擾,有時(shí)候一些對(duì)象莫名其妙就被釋放了,都不知道在哪兒釋放的。在任何對(duì)象dealloc的時(shí)候都打印一個(gè)log,這樣只要看log就知道在哪兒被釋放了。由于對(duì)象的類眾多,重寫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:的實(shí)現(xiàn) */
        class_replaceMethod(class,
                            overrideSel,
                            method_getImplementation(originMethod),
                            method_getTypeEncoding(originMethod));
    }else{
        /** case2:NSMutableDictionary中有-setObject:forKey:的實(shí)現(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下運(yùn)行,因?yàn)锧selector(dealloc)在ARC下編譯器會(huì)報(bào)錯(cuò)

例子有些簡(jiǎn)單和幼稚,現(xiàn)實(shí)中可能沒有這樣的需求,不過這都不是重點(diǎn),重點(diǎn)是……

1. +load VS +initialize

有沒有人想過為什么Swizzling的代碼要放在+load中?why?

=> 因?yàn)镾wizzling的代碼必須要在方法執(zhí)行之前,否則方法都執(zhí)行了,再Swizzling就沒有意義了,而+load方法是main函數(shù)之前調(diào)用的(class添加runtime中的時(shí)候調(diào)用),所以可以保證在方法執(zhí)行前Swizzling……

+initialize方法也可以保證在方法執(zhí)行前調(diào)用,那是否可以將Swizzling的代碼放在這個(gè)里面?

=> 放在+initialize里面大多數(shù)時(shí)候是不會(huì)有問題的,不過這樣做會(huì)有風(fēng)險(xiǎn)。我們知道,+initialize方法是在Class接到第一個(gè)消息之前執(zhí)行,也就是說,多個(gè)有繼承關(guān)系的類,他們的+initialize方法執(zhí)行的順序取決于具體的代碼,哪個(gè)類先接收到第一個(gè)消息,哪個(gè)類的+initialize方法就先執(zhí)行。如果這幾個(gè)類都對(duì)同一個(gè)Method執(zhí)行了Swizzling,這就會(huì)導(dǎo)致他們行為的不確定性,我們不知道哪個(gè)Swizzling先執(zhí)行,從而可能會(huì)產(chǎn)生隱藏的難以發(fā)現(xiàn)的bug。而+load執(zhí)行的順序是確定的。父類的+load先執(zhí)行,之后才執(zhí)行子類

2. dispatch_once

顯而易見,如果多個(gè)線程同時(shí)執(zhí)行同一段Swizzling的代碼,有可能造成混亂,產(chǎn)生我們不希望的結(jié)果,所以一般Swizzling的代碼都需要放在dispatch_once中,不過由于系統(tǒng)保證了+load方法不會(huì)同時(shí)執(zhí)行多次,所以在+load中不加dispatch_once也影響不大,不過為了養(yǎng)成良好的代碼系統(tǒng),加上dispatch_once是最好的

3. Swizzling Class Method

一直以來,我們都僅僅是對(duì)InstanceMethod(實(shí)例方法)進(jìn)行Swizzling,如果我們需要對(duì)Class Method(類方法)進(jìn)行Swizzling,那該怎么做呢?
首先我們來看看答案吧:

void classMethodSwizzling(Class class,SEL originSel,SEL overrideSel)
{
    Class metalClass = object_getClass(class);
    methodSwizzling(metalClass, originSel, overrideSel);
}

我們可以看到,只需要將原來的Class替換成metalClass即可對(duì)ClassMethod進(jìn)行Swizzling。
but why?
要解釋這個(gè),首先我們需要了解一下Class。

Class 關(guān)系圖
Class 關(guān)系圖

Instance,Class,MetalClass的關(guān)系如圖所示,下面2點(diǎn)我需要解釋一下:

  1. Instance的class指針指向Class,Class的class指針指向MetalClass。簡(jiǎn)單來說可以理解為MetalClass的實(shí)例為Class,Class的實(shí)例為真正的實(shí)例Instance。
  2. MetalClass的結(jié)構(gòu)與Class的結(jié)構(gòu)完全相同,他們的區(qū)別只是Class中存放實(shí)例方法,MetalClass中存放類方法。

所以首先,object_getClass()方法傳入一個(gè)實(shí)例對(duì)象,返回實(shí)例對(duì)象的Class。由1可知,傳入class,返回MetalClass。

由于Class中只存放實(shí)例方法,所以要對(duì)類方法進(jìn)行Swizzling必須要在MetalClass上進(jìn)行,并且MetalClass和Class的結(jié)構(gòu)完全相同,所以只需要將原來的Class替換成metalClass即可對(duì)ClassMethod進(jìn)行Swizzling。

Danger

大家可能都知道Method Swizzling是有風(fēng)險(xiǎn)的,但是具體風(fēng)險(xiǎn)在哪里,可能大家就不太明確了。我查找了一些資料,收集了一些Swizzling存在風(fēng)險(xiǎn)的地方,如果大家還發(fā)現(xiàn)其他的風(fēng)險(xiǎn),請(qǐng)聯(lián)系我。

1.Refused by AppStore

危險(xiǎn)系數(shù):★★★★★
遭遇概率:★☆☆☆☆
曾經(jīng)出現(xiàn)過由于Swizzling系統(tǒng)API而被AppStore拒絕的事情,不過我在網(wǎng)上查了很長時(shí)間,這個(gè)事件僅發(fā)生了一次,并且后面的人做同樣的事并沒有遭到拒絕,這個(gè)應(yīng)該也和審核的人有關(guān)。一般情況下應(yīng)該是不會(huì)被拒的,所以這個(gè)危險(xiǎn)系數(shù)極高,但是遭遇概率也非常低。事件的詳細(xì)情況看這里

2.Class Cluster

危險(xiǎn)系數(shù):★★★★☆
遭遇概率:★★☆☆☆
類族的概念在上一篇博客里說起過。簡(jiǎn)單的說,類族就是表面上我們似乎只是在用一個(gè)類,例如NSString(NSNumber,NSArray,NSDictionary等類似),實(shí)際我們使用的是他們的子類,如:__NSCFConstantString,NSPathStore2,NSBigMutableString等,由于這些子類并不公開,我們不知道到底有多少子類,以后還會(huì)不會(huì)增加子類,從而Swizzling的時(shí)候無法對(duì)所有子類覆蓋,導(dǎo)致可能有的情況下使用NSString的方法是Swizzling過的,有的情況下調(diào)用的是未Swizzling的方法。所以對(duì)于這種情況,建議放棄使用Method Swizzling。

3. Swizzling changes the method's arguments

危險(xiǎn)系數(shù):★★☆☆☆
遭遇概率:★★★★☆
Swizzling改變了方法的參數(shù)。我們知道,當(dāng)我們調(diào)用一個(gè)方法時(shí)[obj test],系統(tǒng)會(huì)將其轉(zhuǎn)化為消息發(fā)送objc_msgSend(obj,@selector(test)),并將obj和SEL傳送到方法中,在方法中,可以通過_cmd獲取傳入的SEL,通過剛剛的傳統(tǒng)Swizzling方法Swizzling過后,傳入原函數(shù)的SEL改變了,若原函數(shù)中使用了_cmd,就有可能發(fā)生錯(cuò)誤。幸好,這個(gè)風(fēng)險(xiǎn)是可以避免的,通過以下的修改即可避免這個(gè)風(fēng)險(xiǎn)。

- (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的寫法有些粗魯,過幾天有時(shí)間可以研究一下將其封裝成一個(gè)方法,再過來更新
更新:由于IMP傳入的參數(shù)是可變長參數(shù),因此封裝的方法傳入的參數(shù)必須也為可變長參數(shù),然而目前無法做到將可變長參數(shù)專遞給另一函數(shù),所以這個(gè)地方暫時(shí)無法封裝成一個(gè)方法。如果你有什么其他辦法可以做到,請(qǐng)聯(lián)系我

4.others

這里還列出了一些其他的風(fēng)險(xiǎn),念茜大神在這里對(duì)他進(jìn)行了翻譯,我就不在這里贅述了。感興趣的朋友可以看看,里面還提供了另外一種Method Swizzling的方法,同樣也可以解決3中這個(gè)問題。

參考

Method Swizzling in nshipster

Method Swizzling in codeproject

What are the Dangers of Method Swizzling in Objective C?

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,083評(píng)論 0 9
  • 前言 到了今天終于要"出院"了,要總結(jié)一下住院幾天的收獲,談?wù)凴untime到底能為我們開發(fā)帶來些什么好處。當(dāng)然它...
    一縷殤流化隱半邊冰霜閱讀 23,601評(píng)論 56 317
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評(píng)論 19 139
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,900評(píng)論 33 466
  • 文中的實(shí)驗(yàn)代碼我放在了這個(gè)項(xiàng)目中。 以下內(nèi)容是我通過整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 1,029評(píng)論 0 6

友情鏈接更多精彩內(nèi)容