- 场景需求:在没有一个类的实现源码的情况下,想改变其中一个方法(一般指系统的方法)的实现,除了继承它重写、和借助类别(分类)重名方法暴力抢先之外,如何实现?
- Method Swizzling 原理:
- 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
- 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
- 如图:
- 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,
- 我们可以利用 class_replaceMethod 来修改类,
- 我们可以利用 method_setImplementation 来直接设置某个方法的IMP,
……
- 归根结底,都是偷换了selector的IMP,如下图所示:
示例需求:修改系统UIImage的imageNamed
方法,当我们传图片为空时候,就打印,或者报错;
1.导入头文件
#import <objc/message.h>
2.配置支持运行时如图
3.给UIImage扩充一个分类,添加一个自定义的方法用于替换系统的方法
@interface UIImage (Image)
+ (__kindof UIImage *)dx_imageNamed:(NSString *)imageName;
@end
4.实现这个自定义的方法
+ (UIImage *)dx_imageNamed:(NSString *)imageName
{
// 1.恢复系统方法加载图片功能
// 注意:这里不会死循环,因为此时已经交换了方法,调用这个方法,其实是调用系统的方法
// 注意:这里调用系统的方法不能用super,因为在分类里面不能调用super,分类没有父类
UIImage *image = [UIImage dx_imageNamed:imageName];
// 2.判断图片是否为空
if (image == nil) {
NSLog(@"加载image为空");
}
return image;
}
5.在加载这个分类的时候交换方法
// load方法是应用程序把这个类加载到内存的时候调用,而且只会调用一次,所以在这个方法中实现方法的交换最合适
+ (void)load
{
// 交换方法实现,方法都是定义在类里面
// class_getMethodImplementation:获取方法实现
// class_getInstanceMethod:获取对象
// class_getClassMethod:获取类方法
// IMP:方法实现
// Class:获取哪个类方法
// SEL:获取方法编号,根据SEL就能去对应的类找方法
// 获取系统的方法
Method imageNameMethod = class_getClassMethod([self class], @selector(imageNamed:));
// 获取自定义方法dx_imageNamed
Method dx_imageNamedMethod = class_getClassMethod([UIImage class], @selector(dx_imageNamed:));
// 交换方法实现
method_exchangeImplementations(imageNameMethod, dx_imageNamedMethod);
}
6.在外界调用,测试
- (void)viewDidLoad {
[super viewDidLoad];
// 图片名字是乱编写的
[UIImage imageNamed:@"123"];
}
7.控制台打印输出
加载image为空
这样以后调用imageNamed的时候,就知道图片是否加载成功;
注意:通常你替换一个方法的实现,是希望它在整个程序的生命周期里有效的。也就是说,你会把 method swizzling 修改方法实现的操作放在+(void)load
方法里,并在应用程序的一开始就调用执行