同學們把書合上,我們接著講。
很多人應該對這個方法有所了解,之前面試很多人說這個是什么
iOS黑魔法
,還會拽詞。于是深入一下:什么原理?不知道!怎么用?網上有現成的啊!什么時候用?沒用過啊!本人當場懵逼。所以大家不要背了幾道面試題就寫到自己的專業技能中,反而誤導了大家。
之前我們也說了關于SEL
、Method
以及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
中都寫上對應的方法并且埋點么?如果是伍麗娟
,她肯定先找到一個特定字段標志當前頁面(比如用擴展類加一個特定埋點屬性_下節課再講),然后寫一個方法與系統方法進行交換,調回原方法,后加埋點實現,完美實現!等等... ...