一些概念:
Method Swizzling:是改变一个selector的实际实现的技术。通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。
Aspect Oriented Programming(面向切面编程):一种编程方式,在指定的地方添加一些自定义代码。
例如一些事务琐碎,跟主要业务逻辑无关,在很多地方都有,又很难抽象出来单独的模块。这种程序设计问题,业界给了一个名字:Cross Cutting Concerns
而用 Method Swizzling 动态给指定的方法添加代码,以解决 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 取代方法实现 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 添加方法实现 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
可能出现的错误
1.继承 父类实现了sleep方法 子类没有实现。当进行方法交换的时候 发现将父类的方法实现也交换了
@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 {
//取代实现
class_replaceMethod(objc_getMetaClass(object_getClassName(self)), @selector(run), replaceRun, "");
//添加实现
class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(eat), addEat, "");
//交换实现
method_exchangeImplementations(class_getClassMethod(self, @selector(sleep)), class_getClassMethod(self, @selector(changeMethod)));
}
//取代的实现
void replaceRun() {
NSLog(@"ErrorSub_取代了跑的实现,不想跑");
}
//添加实现
void addEat() {
NSLog(@"ErrorSub_添加了吃的实现,不想吃");
}
//交换的方法
+ (void)changeMethod {
NSLog(@"ErrorSub_交换的方法");
}
@end
原因:当进行方法交换的时候,Sub并没有实现sleep方法 那么便会到父类那边寻找sleep的实现。再进行交换的时候,自然交换的便是父类的sleep方法。
解决:给Sub添加上sleep的实现,可以使用class_addMethod动态添加 在进行交换之前添加
2.多次执行方法交换 并没有出现预期效果
关于这一点,只能说是粗心的原因。
假设:方法1与2 进行交换,预期是调用1实现2 调用2实现1
实现:交换的函数执行了两次
分析:
第一次交换,1->2 2->1
第二次交换,1->1 2->2 (在上次的交换中,1的实现是2的实现,2 的实现是1的实现)。
就是换来换去,结果就晕了。
所以,很多资料上面都建议。将方法的交换写在load里面。但是也不能确保不会有人在子类中写上一个[super load] (手贱)。那么在load中再加上一个dispatch_once
比较完整的方法交换形态
@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方法 为什么要添加 是为了防止自身没有 而父类有 进行方法交换的时候 交换了父类的方法实现 显然这不是我们想要的
//将run的实现添加到 新添加的eat方法上
BOOL isAdd = class_addMethod(objc_getMetaClass(object_getClassName(self)), @selector(eat), method_getImplementation(run), method_getTypeEncoding(run));
//判断
if (isAdd) {
//添加成功 表示自身确实没有eat方法
//那么这个时候只需要将eat的方法实现 替换到 run方法上即可
//是否还记得 判断之前我们添加过SEL为eat IMP为run的实现 的eat方法
class_replaceMethod(self, @selector(run), method_getImplementation(eat), method_getTypeEncoding(eat));
}else {
//添加失败 表示自身存在eat方法
//那么这个时候只需要将 run 与 eat 的IMP(方法实现) 进行交换就好了
method_exchangeImplementations(run, eat);
}
});
}
+ (void)run {
NSLog(@"跑了");
}
+ (void)eat {
NSLog(@"吃了");
}
@end
小例子 利用类别判断当前是那个控制器 同时也可以做很多事 例如统计
@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 {
//这里调用自身并不会引起递归
//记得两个方法的实现已经进行交换了么
//这里的调用 是调用了系统的方法
[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
当然了,实现这种事情,有很多种方法。
但是同时也有很多局限性,可以参考知识链接里面的解释;
在类别中 你没法 super .... 例如[super viewWillAppear]
Logging 的代码都很相似,通过继承或类别重写相关方法是可以把它从主要逻辑中剥离出来。但同时也带来新的问题:
1.你需要继承 UIViewController, UITableViewController, UICollectionViewController 所有这些 ViewController ,或者给他们添加类别;
2.每个 ViewController 里的 ButtonClick 方法命名不可能都一样;
3.你不能控制别人如何去实例化你的子类;
4.对于类别,你没办法调用到原来的方法实现。大多时候,我们重写一个方法只是为了添加一些代码,而不是完全取代它。
5.如果有两个类别都实现了相同的方法,运行时没法保证哪一个类别的方法会给调用。
关于AOP,参考知识链接里面的东西。能够了解的更加透彻
知识链接:
AOP
SEL-IMP
Aspects
体验小Demo:demo
分享一个关于main函数调用之前系统所做的一些事情:
https://www.jianshu.com/p/43db6b0aab8e
https://blog.csdn.net/yu_4074/article/details/54966782