Method Swizzling,methodswizzling

來源:互聯網
上載者:User

Method Swizzling,methodswizzling

文章來自小笨狼的iOS部落格,一直覺得csdn的部落格UI不太好看,看部落格不太爽,所以自己搭建了一個部落格。歡迎各位去連結中看我的部落格。也歡迎大家加QQ群討論iOS技術問題

----------------


Time Flies,好久沒寫部落格了,最近一直在玩設計模式,而設計模式這個東西比較大,自認為還沒到將他們寫出來的時候,等再過一陣吧。正好這幾天看了一個好玩的小東西,覺得不錯,所以分享一下。

緣起

相信大家都用過NSMutableDictionary的-setObject:forKey:方法,使用這個方法當object為nil時,將會崩潰。我們寫代碼時,每次都需要對nil進行判斷,非常麻煩。而且有時候忘了對nil進行判斷,很可能就bug留到上線。上線App中經常會遇到這種崩潰

這時候就想用一個方法,替代-setObject:forKey:,當obj為非nil時正常使用-setObject:forKey:,為nil時不執行任何操作。

對於改變一個方法的實現,有三種方案:override,caregory,swizzling

1.Override

重寫是改變一個方法實現的最普遍的用法,首先建立一個CustomDictionary繼承於NSMutableDictionary,然後重寫-setObject:forKey:,在使用的時候我們使用CustomDictionary.

這樣雖然能達到目的,但也非常麻煩,首先我們必須建立一個新的類繼承於NSMutableDictionary,並對所有使用NSMutableDictionary的老代碼,都得進行修改。如果團隊來了新人,必須得叮囑他不能使用NSMutableDictionary,而要用CustomDictionary……

my god,這也太麻煩了,pass~

2.Category

category是OC中非常棒的東西,傳說其他語言都沒有,那麼讓我們看看使用category怎麼樣?
首先category中的這個方法不能跟-setObject:forKey:重名,不然我們就沒法調用系統的這個-setObject:forKey:了,所以我們命名為-safeSetObject:forKey:,在這個方法中對-setObject:forKey進行重寫。

這個方案比重寫好一些,至少我們不需要建立新的類,並且也完全能達到目的,不過對於老代碼的修改,和對新人的囑咐還是必不可少……

其實我們還有更完美的方案

3.Swizzling

iOS中Method Swizzling不太常見,讓我們來詳細看看什麼是Method Swizzling。

一個方法分為2部分:方法名和方法實現,也即是SEL和IMP。一般情況每個方法都會有固定的SEL和IMP,不可分割。

每個類裡面都有一個方法列表,存放著SEL和IMP的映射關係(圖片來源):

Method Swizzling就是將其中2個方法的SEL和IMP對換一下(圖片來源):

讓本來調用selectorC的方法,去執行selectorN的實現。

下面我們來看一下要實現Method Swizzling,到底要做些什嗎?
首先需要建立一個NSMutableDictionary的category,將用來替換-setObject:forKey:的方法寫進去:

@implementation NSMutableDictionary(Override)- (void)overrideSetObject:(id)anObject forKey:(id <NSCopying>)aKey;{    if (anObject) {        /** 注意:必須調用自己的方法名 */        [self overrideSetObject:anObject forKey:aKey];     }}@end

大家看到這肯定會非常驚訝,在-overrideSetObject:forKey:方法中竟然調用了自己的方法名,這不是死迴圈了嗎?

不要忘了,這個方法是會Swizzling的,也就是說方法內部調用的這個方法名,會指向系統的實現。這裡沒看明白的朋友,可以跳過,後面我會詳細給大家說清楚

方法有了,現在我們該Swizzling了。

首先要注意的是,Swizzling必須要在執行方法之前,不然方法都執行完了,再Swizzling就沒意義了,其次需要看看要不要考慮多線程的問題。

一般情況下,Swizzling的代碼我們都放在+load方法中,因為+load方法是在啟動時執行的(-application:didFinishLaunchingWithOptions:之前),肯定會在使用之前。並且也不用考慮多線程。

+ (void)load{    /** 擷取SEL和Method */    SEL originSel = @selector(setObject:forKey:);    Method originMethod = class_getInstanceMethod([self class], originSel);    SEL overrideSel = @selector(overrideSetObject:forKey:);    Method overrideMethod = class_getInstanceMethod([self class], overrideSel);    /** 分2種case:*/    if (class_addMethod([self class],                        originSel,                        method_getImplementation(overrideMethod),                        method_getTypeEncoding(originMethod)))    {        /** case1:NSMutableDictionary中沒有-setObject:forKey:的實現 */        class_replaceMethod([self class],                            overrideSel,                            method_getImplementation(originMethod),                            method_getTypeEncoding(originMethod));    }else{        /** case2:NSMutableDictionary中有-setObject:forKey:的實現   */        method_exchangeImplementations(originMethod, overrideMethod);    }

對於Swizzling的實現,我們需要分2種case來處理:

case1:NSMutableDictionary中沒有-setObject:forKey:的實現,也許-setObject:forKey:是NSMutableDictionary的父類實現的,我們沒有原始碼,任何情況都可能發生。這種case下。我們需要在NSMutableDictionary中插入-setObject:forKey:方法,再替換他們的IMP部分

case2:NSMutableDictionary中已經存在-setObject:forKey:的實現了,這時候我們就只需要直接替換他們的IMP就ok了

那麼怎麼知道NSMutableDictionary中是否已經存在-setObject:forKey:了呢?很簡單。使用class_addMethod()方法將-setObject:forKey:插入其中,若原來Class未實現,則會插入成功,返回YES,否則返回NO。具體的替換操作的方法我就不詳細解釋了,大家看名字也應該能看明白。

有人可能會問,為什麼要考慮case1這種情況呢?直接全部使用case2不行嗎?答案是不行!因為class_getInstanceMethod()會尋找父類,若原Class未實現,則會替換掉了父類的方法。這並不是我們想要的。

這裡替換掉父類的方法也許影響不大,不過為了保險起見,我們最好還是只替換掉所需Class的方法


這是Swizzling之後的流程圖:

  1. 外部調用-setObject:forKey:的SEL,會跳轉到-overrideSetObject:forKey:的IMP
  2. 在-overrideSetObject:forKey:的IMP中我們會調用-overrideSetObject:forKey:的SEL
  3. -overrideSetObject:forKey:的SEL對應著-setObject:forKey:的IMP

這時候大家應該明白了前面提到的問題:為什麼-overrideSetObject:forKey:的實現中會調用自己了吧?

Finally

Swizzling確實有風險,使用需要謹慎。不過也不能因噎廢食,擔心他的風險而就完全棄之不用,只要我們完全瞭解他的原理,就可以根據具體情況分析是否使用。在這裡,我個人覺得完全可以使用Swizzling替換掉系統的-setObject:forKey:方法,解決線上App,對nil使用-setObject:forKey:崩潰的問題

參考:

Method Replacement for Fun and Profit

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



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.