iOS黑魔法Method Swizzling

  • 场景需求:在没有一个类的实现源码的情况下,想改变其中一个方法(一般指系统的方法)的实现,除了继承它重写、和借助类别(分类)重名方法暴力抢先之外,如何实现?
  • Method Swizzling 原理:
    • 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
    • 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
    • 如图:
iOS黑魔法Method Swizzling_第1张图片
IMP.png

- 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,
- 我们可以利用 class_replaceMethod 来修改类,
- 我们可以利用 method_setImplementation 来直接设置某个方法的IMP,
……
- 归根结底,都是偷换了selector的IMP,如下图所示:

iOS黑魔法Method Swizzling_第2张图片
IMP2.png

示例需求:修改系统UIImage的imageNamed方法,当我们传图片为空时候,就打印,或者报错;

1.导入头文件

#import 

2.配置支持运行时如图


iOS黑魔法Method Swizzling_第3张图片
配置.png

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方法里,并在应用程序的一开始就调用执行

你可能感兴趣的:(iOS黑魔法Method Swizzling)