Runtime之Method Swizzling

Method Swizzling

最近在研究學(xué)習(xí)runtime,其中有一個特性叫method swizzling,即方法替換。它能夠讓你在合適的地方更改一下方法的實現(xiàn)。它功能很強大,如果用好了能夠帶你飛,但是用不好那就是坑啊。下面就開始講解。

Swizzling 原理

在Objective-C中調(diào)用一個方法,其實就是向一個對象發(fā)消息,查找消息的依據(jù)就是selector的名字。我們可以利用runtime的機制,獲取到selector的實現(xiàn)地址然后進行交換,達到方法交換的目的。

每個類都有一個方法列表,存放著selector的名字和IMP實現(xiàn)的映射關(guān)系,IMP在runtime中就是函數(shù)的指針,指向函數(shù)的具體實現(xiàn)。SELMethod的映射關(guān)系如下:


從圖上可以看出一個Selector 對應(yīng)一個 IMP,可以通過Selector 查找到IMP。

但是,現(xiàn)在我們要講一個Selector指向?qū)?yīng)IMP的指針指向其他IMP,即交換兩個Selector的IMP,交換后如下:


我們可以通過Swizzling技術(shù)來實現(xiàn)上述的效果,也就是給給對象發(fā)送Selector C消息的時候,本來指向的是IMP C,現(xiàn)在讓它指向IMP X的方法實現(xiàn)。

在+load方法中使用

Swizzling應(yīng)該在+load方法中使用,因為load方法可以保證在類最開始初始化加載的時候調(diào)用。因為Swizzling method的影響是全局的,應(yīng)該放在最保險的地方去處理。 +load方法能夠保證在類的初始化的時候就加載,保證統(tǒng)一性。試想若是臨時的調(diào)用一下swizzling,而沒有及時的將方法給替換回來就會產(chǎn)生很大的影響。同時也不能在 +initialize方法中使用,因為這個方法可能一直未被使用,因為這個類可能永遠不能被調(diào)用。類文件在工程中,一定會被加載,因此可以保證+load方法被調(diào)用,但是+initialize并不能保證被調(diào)用。

在Dispatch_once中保證只交換一次。

方法交換即使在多線程中應(yīng)該也是調(diào)用一次,因此在Dispatch_once使用能夠保證之交換一次,保證線程安全,因為我們需要將它加到Swizzling的使用安全規(guī)范中

交換IMP的方法

我們定義一個NSObject的分類,這樣在Objective-C的體系下所有繼承NSObject的對象都能有這個方法替換的功能。

#import "NSObject+Swizzling.h"
#import <objc/runtime.h>

@implementation NSObject (Swizzling)

+ (void)switchSelector:(SEL)originSelector withSwizzledSelector:(SEL)swizzledSelector
{
    Method originMethod = class_getInstanceMethod([self class], originSelector);
    Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
    
    BOOL notHaveMethod =  class_addMethod([self class], originSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
    if (notHaveMethod) {
        //如果沒有方法,表明添加方法成功
        class_replaceMethod([self class], swizzledSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
    }else{
        //如果有方法,直接替換
        method_exchangeImplementations(originMethod, swizzledMethod);
    }
}

因為方法可能不是在這個類中,可能在父類中,因此先嘗試在該類中添加該方法,如果添加成功,表明這個類沒有這個方法,然后直接替換實現(xiàn)就可以了。如果沒有添加成功,說明這個類以及有這個方法了,那就直接替換,用method_exchangeImplementations這個方法,因為這個方法是原子操作的,是線程安全的。不要用下面的方式:

IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);

你點擊method_exchangeImplementations的介紹就可以看到,它的本質(zhì)就是這樣的方式來寫的,但是它在內(nèi)部做了線程安全操作。

簡單使用

首先定義一個NSMutableArray的分類NSMutableArray+Swizzling,并在NSMutableArray+Swizzling.m文件中實現(xiàn)如下方法:

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        [objc_getClass("__NSArrayM")  swizzleSelector:@selector(addObject:) withSwizzledSelector:NSSelectorFromString(@"dg_addObject:")];
        
    });
}

- (id)dg_addObject:(id)obj
{
    if (!obj) {
        NSLog(@"您添加的對象為nil");
        return nil;
    }
    return  [self dg_addObject:obj];
    
}

+load方法中實現(xiàn)在dispatch_once中替換原生的addObject方法為自定義的dg_addObject方法。

NSMutableArray *tempArray = [NSMutableArray array];
[tempArray addObject:nil]; //添加nil,不會崩潰
NSLog(@"The array's last object is :%@",tempArray.lastObject);
//會發(fā)現(xiàn)打印結(jié)果為:
//NSArray is empty
//The array's last object is :(null)

在我們自己實現(xiàn)的方法中,對于如果添加盡力的對象是nil,我們不進行任何處理,只是打印log,這樣就不會出現(xiàn)我們經(jīng)常遇到的nil為空就崩的錯誤了。是不是很6?我們即可以做防nil操作,還可以做一些其他操作。

上面再替換方法的時候的調(diào)用對象為objc_getClass("__NSArrayM"),這主要原因是在Cocoa中,NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等類,真實執(zhí)行操作的不是這些對象,它們的真身其實是這樣的:

method3.png

這里我發(fā)現(xiàn)有人寫的是__NSArrayI,本人在XCode中發(fā)現(xiàn)現(xiàn)在是__NSArray0,可能是版本的問題吧。具體可參考類簇。

小結(jié)

Method SWizzling 是個很有意思的技術(shù),用來能夠做許多強大的功能,但是用Method Swizzling如果使用不當(dāng),也會引起很多問題。不過只有了解它的實現(xiàn)原理,正確的使用,還是能給我們的開發(fā)帶來很大便利。

本文demo地址:runtime method swizzling

參考文獻:

Runtime Method Swizzling

iOS黑魔法-Method Swizzling

在查看本文的時候,如果發(fā)現(xiàn)錯誤或者有什么交流的,可以給我聯(lián)系:ls_xyq@126.com.

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

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

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