【遷移】MethodSwizzling(二)

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覆劈。

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

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

  1. Instance的class指針指向Class炮障,Class的class指針指向MetalClass。簡單來說可以理解為MetalClass的實例為Class鹦筹,Class的實例為真正的實例Instance铝阐。
  2. 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 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)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市剃浇,隨后出現(xiàn)的幾起案子巾兆,更是在濱河造成了極大的恐慌,老刑警劉巖虎囚,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件角塑,死亡現(xiàn)場離奇詭異,居然都是意外死亡淘讥,警方通過查閱死者的電腦和手機圃伶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蒲列,“玉大人窒朋,你說我怎么就攤上這事〖掂郑” “怎么了炼邀?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剪侮。 經(jīng)常有香客問我拭宁,道長洛退,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任杰标,我火速辦了婚禮兵怯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腔剂。我一直安慰自己媒区,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布掸犬。 她就那樣靜靜地躺著袜漩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪湾碎。 梳的紋絲不亂的頭發(fā)上宙攻,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音介褥,去河邊找鬼座掘。 笑死,一個胖子當著我的面吹牛柔滔,可吹牛的內(nèi)容都是我干的溢陪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼睛廊,長吁一口氣:“原來是場噩夢啊……” “哼形真!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起超全,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤没酣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后卵迂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡绒净,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年见咒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挂疆。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡改览,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缤言,到底是詐尸還是另有隱情宝当,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布胆萧,位于F島的核電站庆揩,受9級特大地震影響俐东,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜订晌,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一虏辫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锈拨,春花似錦砌庄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缝彬,卻和暖如春萌焰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跌造。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工杆怕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壳贪。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓陵珍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親违施。 傳聞我的和親對象是個殘疾皇子互纯,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

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