Runtime奇技淫巧之method_exchangeImplementations

同學們把書合上,我們接著講。

很多人應該對這個方法有所了解,之前面試很多人說這個是什么iOS黑魔法,還會拽詞。于是深入一下:什么原理?不知道!怎么用?網上有現成的啊!什么時候用?沒用過啊!本人當場懵逼。所以大家不要背了幾道面試題就寫到自己的專業技能中,反而誤導了大家。
之前我們也說了關于SELMethod以及IMP分別是個什么東西(想知道點我),今天我們就來說一下這個交換方法的函數。

引言

上一篇我們說了對于實例變量的一些操作(也就是Class結構體中的ivars),順著套路我們研究一下他的方法列表methodLists以及元類的methodLists

交換方法圖解

我們說過方法調用的過程,歸根到底是函數指針的調用,在OC的動態語言特性下,我們可以改變Method結構體中IMP函數實現指針的指向:(我們以實例方法為例)
方法交換前:


方法交換后:

??:很多人認為方法交換只局限于交換同類的方法,這是一種對于原理模糊不清造成的誤解。方法交換不止限于同類,兩個不同的類的方法同樣可以交換,甚至不同類不同參數數量的類方法和實例方法都能進行交換。
??:方法交換也不一定要在+(void)load;中進行交換。只不過在+(void)load;方法中統一交換更方便管理,不易出現問題。
??:當你交換NSArray等類簇的方法的時候,你會發現沒有任何卵用,因為這些類簇類,其實是一種抽象工廠的設計模式。抽象工廠內部有很多其它繼承自當前類的子類,抽象工廠類會根據不同情況,創建不同的抽象對象來進行調用。關于這些類簇調用方法的本類請自行Google。

創建一個Person類,實現+(void)eat;-(void)eat;方法。在VC中實現+(void)eat_VC;方法。在VC中加入如下代碼:

+(void)load{
    //交換類方法
    Method originalMethod_class = class_getClassMethod(self, NSSelectorFromString(@"eat_VC"));
    Method swizzledMethod_class = class_getClassMethod(NSClassFromString(@"Person"), NSSelectorFromString(@"eat"));
    method_exchangeImplementations(originalMethod_class, swizzledMethod_class);
    //交換實例方法
    Method originalMethod_id = class_getInstanceMethod(self, NSSelectorFromString(@"viewDidLoad"));
    Method swizzledMethod_id = class_getInstanceMethod(NSClassFromString(@"Person"), NSSelectorFromString(@"eat"));
    method_exchangeImplementations(originalMethod_id, swizzledMethod_id);
}

你會發現ViewController的方法被Person的方法替換掉了。

代碼分享

下面分享一下封裝的代碼塊,對于剛才說到的奇怪的交換方式開發中并不多見,我們只對于同類提供方法:

NSObject (Runtime)
.h
#pragma mark - method_exchangeImplementations
/**
 *替換實例方法(在+load中調用)
 */
+ (void)swizzleSelector:(SEL)originalSelector withSelector:(SEL)swizzledSelector;
/**
 *替換類方法(在+load中調用)
 */
+ (void)swizzleClassSelector:(SEL)originalSelector withClassSelector:(SEL)swizzledSelector;
--------------------------------------------------------------------
.m
#pragma mark - 交換方法
+ (void)swizzleSelector:(SEL)originalSelector withSelector:(SEL)swizzledSelector {
    Class class = [self class];
    
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    BOOL didAddMethodInit=class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethodInit) {
        class_addMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

+ (void)swizzleClassSelector:(SEL)originalSelector withClassSelector:(SEL)swizzledSelector {
    Class class = [self class];
    
    Method originalMethod = class_getClassMethod(class, originalSelector);
    Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
    if ((int)originalMethod != 0 && (int)swizzledMethod != 0) {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

有人說了,這個又有什么卵用,我就是覺得好牛逼,其他我沒有一點思路啊!

實用場景

  • 當我們一個功能模塊功能點很多時,我們可以按照功能點創建分類,需要調用相同的方法(比如:-(void)viewDidLoad;)我們可以在每個擴展類中都寫一個對應的然后進行交換。有人說,我在本類中調用擴展類中的方法就可以了啊,為毛我要交換方法?首先,站在一個程序員的角度,.h中公開的東西越少,別人對你的威脅就越小,另外,相同功能模塊的東西放到本模塊代碼的擴展類中才是正確選擇,至少你直接刪除文件的時候,就相當于去掉了這個功能而不報錯。(本人野套路,不服不要來辯)

  • 對于需求修改的擴展,比如你們公司有個超級牛逼的應用,100個ViewController,展示100張Image,迫不得已你使用了100次+ (nullable UIImage *)imageNamed:(NSString *)name;方法。然后你們偉大的產品經理要做一個大的迭代,在展示圖片的同時,打印圖片的大小。你需要去改100個地方么?如果是伍麗娟他肯定替換掉原來的方法,在原有的基礎上添加打印大小的代碼。(??:調用原系統方法時注意循環引用問題)
    包括替換方法防止數組越界,以及防止在數組和字典中加入nil,想法同屬一類。

  • 對于通用模塊基于系統方法的擴展,比如我們要統計頁面停留時長,那么我們需要在- (void)viewDidAppear:(BOOL)animated;以及- (void)viewDidDisappear:(BOOL)animated;中進行埋點,但是你要到每一個UIViewController中都寫上對應的方法并且埋點么?如果是伍麗娟,她肯定先找到一個特定字段標志當前頁面(比如用擴展類加一個特定埋點屬性_下節課再講),然后寫一個方法與系統方法進行交換,調回原方法,后加埋點實現,完美實現!

  • 等等... ...

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,765評論 18 399
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,210評論 30 471
  • 每次坐硬座都會感覺有種時光穿梭的錯覺,尤其是在夜晚,抬頭看著行李架上方的燈,就會突然間想起我們一起回家或上學的...
    文田心元閱讀 284評論 0 1
  • 我是個好孩子~
    碼代碼的沉魚閱讀 108評論 0 0