Runtime Method Swizzling技術(shù)

運行時確實是個好東西扶踊,俗話說學(xué)會了Runtime還不夠裙盾,要懂得如何運用它來為項目帶來便利实胸,那才叫做真正的懂它。其實番官,很多方面我們需要用到它庐完,只是很多時候我們不知道它的存在或者根本不會去了解和深入學(xué)習(xí)它而已。譬如徘熔,不同iOS版本的API兼容問題或是替換原有系統(tǒng)的IMP门躯,又或是通過反射來獲取系統(tǒng)的私有API,還有在APP安全防護和攻擊時也用到它近顷。
說到Runtime生音,不得不說說它的swizzling技術(shù)宁否。這個名詞好像在14年那會挺吸眼球的,在逆向工程中偶爾會見到它的身影缀遍。其實慕匠,說的通俗點就是用自己寫的方法偷換系統(tǒng)的IMP。
最常見的就是通過一個例子說明來談?wù)勥@個技術(shù)域醇。

+ (void)load { 
      static dispatch_once_t onceToken; 
      dispatch_once(&onceToken, ^{ 
           Class aClass = [self class]; 
           SEL originalSelector = @selector(viewWillAppear:); 
           SEL swizzledSelector = @selector(xxx_viewWillAppear:); 

          // 通過實例方法來獲取Method
           Method originalMethod = class_getInstanceMethod(aClass, originalSelector); 
           Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector); 
    
           // 這個是獲取類名台谊,既是獲取metaClass.
           // Class aClass = object_getClass((id)self);
           // 通過類方法來獲取Method
           // Method originalMethod = class_getClassMethod(aClass, originalSelector);
           // Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);

          // 為類添加新方法
           BOOL didAddMethod = 
           class_addMethod(aClass, 
                        originalSelector, 
                        method_getImplementation(swizzledMethod), 
                        method_getTypeEncoding(swizzledMethod)); 

           if (didAddMethod) { 
                 class_replaceMethod(aClass, 
                 swizzledSelector, 
                 method_getImplementation(originalMethod), 
                 method_getTypeEncoding(originalMethod)); 
           } else { 
                 method_exchangeImplementations(originalMethod, swizzledMethod); 
           } 
      }); 
} 

#pragma mark - Method Swizzling 
- (void)xxx_viewWillAppear:(BOOL)animated { 
      [self xxx_viewWillAppear:animated]; 
      NSLog(@"viewWillAppear: %@", self); 
} 

備注:class_addMethod是為該類動態(tài)添加方法Method,具體就是通過原有的selector來擴展新的IMP譬挚,
通過它來判斷該類是否已經(jīng)動態(tài)為其添加過該方法锅铅,
如果添加過了,通過class_replaceMethod來替換原有的selector實現(xiàn)减宣,從而達到對換這兩個selector來交換其實現(xiàn)的IMP盐须。否則,通過method_exchangeImplementations來交換兩者的IMP實現(xiàn)漆腌。
這里強調(diào)一下+(void)load贼邓,這個方法是系統(tǒng)第一次裝載程序到內(nèi)存時調(diào)用,而且只調(diào)用一次闷尿,在程序開啟時塑径,程序主要把所有.m文件都加載到內(nèi)存中。還有與之對應(yīng)的方法是+ (void)initialize. 官方介紹如下:

+(void)initialize
The runtime sends initialize to each class in a program exactly one time just before the class,     
or any class that inherits from it, is sent its first message from within the program. (Thus the    
method may never be invoked if the class is not used.) The runtime sends the initialize 
message to classes in a thread-safe manner. Superclasses receive this message before their 
subclasses.

+(void)load
The load message is sent to classes and categories that are both dynamically loaded and  
statically linked, but only if the newly loaded class or category implements a method that can  
respond.

The order of initialization is as follows:

All initializers in any framework you link to.
All +load methods in your image.
All C++ static initializers and C/C++ __attribute__(constructor) functions in your image.
All initializers in frameworks that link to you.
In addition:

A class’s +load method is called after all of its superclasses’ +load methods.
A category +load method is called after the class’s own +load method.
In a custom implementation of load you can therefore safely message other unrelated classes    
from the same image, but any load methods implemented by those classes may not have run yet.

Apple的文檔很清楚地說明了initialize和load的區(qū)別在于:load是只要類所在文件被引用就會被調(diào)用填具,而initialize是在類或者其子類的第一個方法被調(diào)用前調(diào)用统舀。所以如果類沒有被引用進項目,就不會有l(wèi)oad調(diào)用劳景;但即使類文件被引用進來誉简,但是沒有使用,那么initialize也不會被調(diào)用枢泰。

它們的相同點在于:方法只會被調(diào)用一次描融。(其實這是相對runtime來說的,后邊會做進一步解釋)衡蚂。

文檔也明確闡述了方法調(diào)用的順序:父類(Superclass)的方法優(yōu)先于子類(Subclass)的方法,類中的方法優(yōu)先于類別(Category)中的方法骏庸。

似乎有點扯遠了毛甲。。具被。玻募。。一姿。

這個技術(shù)真的是很常見七咧,同時可以為我們省去很多麻煩和瑣碎的細(xì)節(jié)跃惫。譬如,如果一個控件的API在iOS7上沒有這個方法艾栋,而iOS8及以上有這個方法爆存,可以通過寫這個控件的分類來判斷不同版本下同時調(diào)用這個方法,但是在該分類中通過版本判斷蝗砾,在iOS7及以下用自己實現(xiàn)類似系統(tǒng)API的方法來替換系統(tǒng)的該方法先较,從而達到不用修改原有的代碼。

還有就是在對NSArray悼粮,NSMutableArray闲勺,NSDictionary,NSMutableDictionary中扣猫,通過字面量訪問方式或者通過objectAtIndex等方法進行訪問時菜循,如果服務(wù)器那邊不小心傳入nil來插入數(shù)組或者字典或訪問越界數(shù)據(jù),都會導(dǎo)致應(yīng)用崩潰申尤。所以癌幕,通過實現(xiàn)NSArray等數(shù)據(jù)結(jié)構(gòu)的分類,來對nil瀑凝,越界等進行判斷處理序芦,防止程序崩潰。但是使用這個技術(shù)粤咪,如果數(shù)據(jù)出現(xiàn)錯誤的情況谚中,很難通過該方法來查找bug,所以要謹(jǐn)慎使用之寥枝。

由此宪塔,我們可以根據(jù)上面所學(xué),對NSArray囊拜、NSMutableArray某筐、NSDictionary、NSMutableDictionary等類進行Method Swizzling冠跷,但是南誊,你發(fā)現(xiàn)Method Swizzling根本就不起作用,代碼也沒寫錯啊蜜托,到底是為什么抄囚?這是因為Method Swizzling對NSArray這些的類簇是不起作用的。因為這些類簇類橄务,其實是一種抽象工廠的設(shè)計模式幔托。抽象工廠內(nèi)部有很多其它繼承自當(dāng)前類的子類,抽象工廠類會根據(jù)不同情況,創(chuàng)建不同的抽象對象來進行使用重挑。例如我們調(diào)用NSArray的objectAtIndex:方法嗓化,這個類會在方法內(nèi)部判斷,內(nèi)部創(chuàng)建不同抽象類進行操作谬哀。

所以也就是我們對NSArray類進行操作其實只是對父類進行了操作刺覆,在NSArray內(nèi)部會創(chuàng)建其他子類來執(zhí)行操作,真正執(zhí)行操作的并不是NSArray自身玻粪,所以我們應(yīng)該對其“真身”進行操作隅津。NSArray的真身是__NSArrayI,而NSMutableArray的真身是__NSArrayM, NSDictionary的真身是__NSDictionaryI,NSMutableDictionary的真身是__NSDictionaryM。這幾個東東是不是很熟悉啊劲室,沒錯伦仍,還記得當(dāng)崩潰時控制臺輸出的信息中就有這幾個關(guān)鍵字的出現(xiàn)嗎?好吧很洋,這種事情你們自己去發(fā)掘好了充蓝。
代碼如下:

#import "NSArray+Extension.h"
#import <objc/runtime.h>
@implementation NSArray (Extension)
+ (void)load {
       Method originMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
       Method swizzleMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(wt_objectAtIndex:));
       method_exchangeImplementations(originMethod, swizzleMethod);
}

// 分類記得要加前綴,否則會和別人寫的分類沖突喉磁,導(dǎo)致只有一個方法映射到IMP中谓苟。
- (id)wt_objectAtIndex:(NSUInteger)index {
       if (self.count-1 < index) {
           @try {
                return [self lxz_objectAtIndex:index];
       }
       @catch (NSException *exception) {
           // 打印崩潰信息,方便調(diào)試
           NSLog(@"---------- %s Crash Method %s  ----------\n", class_getName(self.class), __func__);
           NSLog(@"%@", [exception callStackSymbols]);
           return nil;
       }
       @finally {}
        } 
        else {
             return [self wt_objectAtIndex:index];
        }
 }
@end

總之,合適的地方恰當(dāng)?shù)倪\用該技術(shù),會達到事半功倍的效果飘庄,而且這也給自己的技術(shù)功底提升了不少呢澈侠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眠蚂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖隧哮,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異座舍,居然都是意外死亡沮翔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門曲秉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來采蚀,“玉大人,你說我怎么就攤上這事承二〔妫” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵矢洲,是天一觀的道長。 經(jīng)常有香客問我缩焦,道長读虏,這世上最難降的妖魔是什么责静? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮盖桥,結(jié)果婚禮上灾螃,老公的妹妹穿的比我還像新娘。我一直安慰自己揩徊,他們只是感情好腰鬼,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著塑荒,像睡著了一般熄赡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上齿税,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天彼硫,我揣著相機與錄音,去河邊找鬼凌箕。 笑死拧篮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的牵舱。 我是一名探鬼主播串绩,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼芜壁!你這毒婦竟也來了礁凡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沿盅,失蹤者是張志新(化名)和其女友劉穎把篓,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腰涧,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡韧掩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了窖铡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疗锐。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖费彼,靈堂內(nèi)的尸體忽然破棺而出滑臊,到底是詐尸還是另有隱情,我是刑警寧澤箍铲,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布雇卷,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏关划。R本人自食惡果不足惜小染,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贮折。 院中可真熱鬧裤翩,春花似錦、人聲如沸调榄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽每庆。三九已至筐带,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扣孟,已是汗流浹背烫堤。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凤价,地道東北人鸽斟。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像利诺,于是被迫代替她去往敵國和親富蓄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

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