一些概念:
Method Swizzling:是改變一個selector的實際實現(xiàn)的技術。通過這一技術,我們可以在運行時通過修改類的分發(fā)表中selector對應的函數(shù),來修改方法的實現(xiàn)。
Aspect Oriented Programming(面向切面編程):一種編程方式,在指定的地方添加一些自定義代碼。
例如一些事務瑣碎,跟主要業(yè)務邏輯無關,在很多地方都有,又很難抽象出來單獨的模塊。這種程序設計問題,業(yè)界給了一個名字:Cross Cutting Concerns
而用 Method Swizzling 動態(tài)給指定的方法添加代碼,以解決 Cross Cutting Concerns 的編程方式就叫:Aspect Oriented Programming(面向切面編程)
初識三種method-swizzing的使用
1.method_exchangeImplementations 方法交換 交換SEL->IMP
@interface Change : NSObject
+ (void)eat;
+ (void)run;
@end
@implementation Change
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method eat = class_getClassMethod(self, @selector(eat));
Method run = class_getClassMethod(self, @selector(run));
method_exchangeImplementations(eat, run);
});
}
+ (void)eat {
NSLog(@"吃了");
}
+ (void)run {
NSLog(@"正準備跑");
}
@end
2.class_replaceMethod 取代方法實現(xiàn) IMP
@interface Replace : NSObject
+ (void)sleep;
@end
@implementation Replace
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
class_replaceMethod(self, @selector(sleep), sleepFunction, "");
});
}
+ (void)sleep {
NSLog(@"馬上睡");
}
void sleepFunction() {
NSLog(@"還不想睡");
}
@end
3.class_addMethod 添加方法實現(xiàn) IMP
@interface AddMethod : NSObject
+ (void)work;
@end
@implementation AddMethod
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(work), workFunction, "");
});
}
void workFunction() {
NSLog(@"上班");
}
@end
可能出現(xiàn)的錯誤
1.繼承 父類實現(xiàn)了sleep方法 子類沒有實現(xiàn)。當進行方法交換的時候 發(fā)現(xiàn)將父類的方法實現(xiàn)也交換了
@interface Super : NSObject
+ (void)run;
+ (void)sleep;
+ (void)eat;
@end
@implementation Super
+ (void)run {
NSLog(@"ErrorSuper_跑了");
}
+ (void)sleep {
NSLog(@"ErrorSuper_睡了");
}
+ (void)eat {
NSLog(@"ErrorSuper_吃了");
}
@end
@interface Sub : Super
+ (void)run;
+ (void)sleep;
+ (void)eat;
@end
@implementation Sub
+ (void)load {
//取代實現(xiàn)
class_replaceMethod(objc_getMetaClass(object_getClassName(self)), @selector(run), replaceRun, "");
//添加實現(xiàn)
class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(eat), addEat, "");
//交換實現(xiàn)
method_exchangeImplementations(class_getClassMethod(self, @selector(sleep)), class_getClassMethod(self, @selector(changeMethod)));
}
//取代的實現(xiàn)
void replaceRun() {
NSLog(@"ErrorSub_取代了跑的實現(xiàn),不想跑");
}
//添加實現(xiàn)
void addEat() {
NSLog(@"ErrorSub_添加了吃的實現(xiàn),不想吃");
}
//交換的方法
+ (void)changeMethod {
NSLog(@"ErrorSub_交換的方法");
}
@end
原因:當進行方法交換的時候,Sub并沒有實現(xiàn)sleep方法 那么便會到父類那邊尋找sleep的實現(xiàn)。再進行交換的時候,自然交換的便是父類的sleep方法。
解決:給Sub添加上sleep的實現(xiàn),可以使用class_addMethod動態(tài)添加 在進行交換之前添加
2.多次執(zhí)行方法交換 并沒有出現(xiàn)預期效果
關于這一點,只能說是粗心的原因。
假設:方法1與2 進行交換,預期是調(diào)用1實現(xiàn)2 調(diào)用2實現(xiàn)1
實現(xiàn):交換的函數(shù)執(zhí)行了兩次
分析:
第一次交換,1->2 2->1
第二次交換,1->1 2->2 (在上次的交換中,1的實現(xiàn)是2的實現(xiàn),2 的實現(xiàn)是1的實現(xiàn))。
就是換來換去,結(jié)果就暈了。
所以,很多資料上面都建議。將方法的交換寫在load里面。但是也不能確保不會有人在子類中寫上一個[super load] (手賤)。那么在load中再加上一個dispatch_once
比較完整的方法交換形態(tài)
@interface Complete : NSObject
+ (void)run;
+ (void)eat;
@end
@implementation Complete
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//獲取需要交換的方法
Method run = class_getClassMethod(self, @selector(run));
Method eat = class_getClassMethod(self, @selector(eat));
//添加eat方法 為什么要添加 是為了防止自身沒有 而父類有 進行方法交換的時候 交換了父類的方法實現(xiàn) 顯然這不是我們想要的
//將run的實現(xiàn)添加到 新添加的eat方法上
BOOL isAdd = class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(eat), method_getImplementation(run), method_getTypeEncoding(run));
//判斷
if (isAdd) {
//添加成功 表示自身確實沒有eat方法
//那么這個時候只需要將eat的方法實現(xiàn) 替換到 run方法上即可
//是否還記得 判斷之前我們添加過SEL為eat IMP為run的實現(xiàn) 的eat方法
class_replaceMethod(self, @selector(run), method_getImplementation(eat), method_getTypeEncoding(eat));
}else {
//添加失敗 表示自身存在eat方法
//那么這個時候只需要將 run 與 eat 的IMP(方法實現(xiàn)) 進行交換就好了
method_exchangeImplementations(run, eat);
}
});
}
+ (void)run {
NSLog(@"跑了");
}
+ (void)eat {
NSLog(@"吃了");
}
@end
小例子 利用類別判斷當前是那個控制器 同時也可以做很多事 例如統(tǒng)計
@implementation UIViewController (swizzing)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));
Method swizzMethod = class_getInstanceMethod(self, @selector(swizz_viewWillAppear:));
BOOL isAdd = class_addMethod(self, @selector(viewWillAppear:), method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
if (isAdd) {
class_replaceMethod(self, @selector(swizz_viewWillAppear:), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else {
method_exchangeImplementations(originalMethod, swizzMethod);
}
});
}
- (void)swizz_viewWillAppear:(BOOL)animated {
//這里調(diào)用自身并不會引起遞歸
//記得兩個方法的實現(xiàn)已經(jīng)進行交換了么
//這里的調(diào)用 是調(diào)用了系統(tǒng)的方法
[self swizz_viewWillAppear:animated];
if ([self isKindOfClass:[ViewController class]]) {
NSLog(@"當前控制器是ViewController");
}else if ([self isKindOfClass:[PVC1 class]]) {
NSLog(@"當前控制器是PVC1");
}else if ([self isKindOfClass:[PVC2 class]]) {
NSLog(@"當前控制器是PVC2");
}else if ([self isKindOfClass:[PVC3 class]]) {
NSLog(@"當前控制器是PVC3");
}
}
@end
當然了,實現(xiàn)這種事情,有很多種方法。
但是同時也有很多局限性,可以參考知識鏈接里面的解釋;
在類別中 你沒法 super .... 例如[super viewWillAppear]
Logging 的代碼都很相似,通過繼承或類別重寫相關方法是可以把它從主要邏輯中剝離出來。但同時也帶來新的問題:
1.你需要繼承 UIViewController, UITableViewController, UICollectionViewController 所有這些 ViewController ,或者給他們添加類別;
2.每個 ViewController 里的 ButtonClick 方法命名不可能都一樣;
3.你不能控制別人如何去實例化你的子類;
4.對于類別,你沒辦法調(diào)用到原來的方法實現(xiàn)。大多時候,我們重寫一個方法只是為了添加一些代碼,而不是完全取代它。
5.如果有兩個類別都實現(xiàn)了相同的方法,運行時沒法保證哪一個類別的方法會給調(diào)用。
關于AOP,參考知識鏈接里面的東西。能夠了解的更加透徹
知識鏈接:
AOP
SEL-IMP
Aspects
體驗小Demo:demo
分享一個關于main函數(shù)調(diào)用之前系統(tǒng)所做的一些事情:
http://www.itdecent.cn/p/43db6b0aab8e
https://blog.csdn.net/yu_4074/article/details/54966782