OC中做方法交换时,没有直接使用method_exchangeImplementations,而是与class_addMethod一起使用。
if(class_addMethod([self class], test11, method_getImplementation(method1), "v@:"))
{
class_replaceMethod([self class], test1, method_getImplementation(method11), "v@:");
}else{
method_exchangeImplementations(method1, method3);
}
那为什么这么用,会有什么好处。先做个对比测试,看下面的代码。
//
// MethodBase.h
// BlockTest
//
// Created by net263 on 2022/1/12.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MethodBase : NSObject
-(void)test3;
-(void)exchangeBase;
@end
NS_ASSUME_NONNULL_END
//
// MethodBase.m
// BlockTest
//
// Created by net263 on 2022/1/12.
//
#import "MethodBase.h"
#import <objc/runtime.h>
@implementation MethodBase
- (void)test3{
[self test11];
NSLog(@"test3");
}
- (void)exchangeBase{
SEL test1 = @selector(test1);
SEL test11 = @selector(test11);
SEL test3 = @selector(test3);
Method method1 = class_getInstanceMethod([self class], test1);
Method method11 = class_getInstanceMethod([self class], test11);
Method method3 = class_getInstanceMethod([self class], test3);
method_exchangeImplementations(method11, method1);
// if(class_addMethod([self class], test11, method_getImplementation(method1), "v@:"))
// {
// class_replaceMethod([self class], test1, method_getImplementation(method11), "v@:");
// }else{
// method_exchangeImplementations(method11, method1);
// }
}
-(void)test11{
NSLog(@"test11");
}
@end
//
// MethodEx.h
// BlockTest
//
// Created by net263 on 2022/1/12.
//
#import <Foundation/Foundation.h>
#import "MethodBase.h"
NS_ASSUME_NONNULL_BEGIN
@interface MethodEx : MethodBase
@end
NS_ASSUME_NONNULL_END
//
// MethodEx.m
// BlockTest
//
// Created by net263 on 2022/1/12.
//
#import "MethodEx.h"
#import <objc/objc.h>
#import <objc/runtime.h>
@implementation MethodEx
- (void)test1{
NSLog(@"test1");
}
@end
MethodEx继承MethodBase,MethodBase中有test11及test3方法,还有执行交换的方法exchangeBase。
子类MethodBase有test1方法。
先简单了解下exchangeBase交换方法的内容:
- (void)exchangeBase{
SEL test1 = @selector(test1);
SEL test11 = @selector(test11);
SEL test3 = @selector(test3);
Method method1 = class_getInstanceMethod([self class], test1);
Method method11 = class_getInstanceMethod([self class], test11);
Method method3 = class_getInstanceMethod([self class], test3);
method_exchangeImplementations(method11, method1);
// if(class_addMethod([self class], test11, method_getImplementation(method1), "v@:"))
// {
// class_replaceMethod([self class], test1, method_getImplementation(method11), "v@:");
// }else{
// method_exchangeImplementations(method11, method1);
// }
}
这段代码是将test1与test11进行交换。
外部调用的代码如下:
MethodEx *methodEx = [[MethodEx alloc] init];
NSLog(@"交换前");
[methodEx test3];
NSLog(@"--------------");
NSLog(@"交换后");
[methodEx exchangeBase];
[methodEx test3];
NSLog(@"--------------");
NSLog(@"新的子类实例");
MethodEx *methodEx1 = [[MethodEx alloc] init];
[methodEx1 test3];
NSLog(@"--------------");
NSLog(@"父类实例");
MethodBase *base = [[MethodBase alloc] init];
[base test3];
- 先看直接调用method_exchangeImplementations的情况
2-01-12 16:31:19.906047+0800 BlockTest[1112:462703] 交换前
2022-01-12 16:31:19.906088+0800 BlockTest[1112:462703] test11
2022-01-12 16:31:19.906133+0800 BlockTest[1112:462703] test3
2022-01-12 16:31:19.906166+0800 BlockTest[1112:462703] --------------
2022-01-12 16:31:19.906204+0800 BlockTest[1112:462703] 交换后
2022-01-12 16:31:19.906499+0800 BlockTest[1112:462703] test1
2022-01-12 16:31:19.906741+0800 BlockTest[1112:462703] test3
2022-01-12 16:31:19.906788+0800 BlockTest[1112:462703] --------------
2022-01-12 16:31:19.906834+0800 BlockTest[1112:462703] 新的子类实例
2022-01-12 16:31:19.906871+0800 BlockTest[1112:462703] test1
2022-01-12 16:31:19.906950+0800 BlockTest[1112:462703] test3
2022-01-12 16:31:19.907036+0800 BlockTest[1112:462703] --------------
2022-01-12 16:31:19.907401+0800 BlockTest[1112:462703] 父类实例
2022-01-12 16:31:19.907440+0800 BlockTest[1112:462703] test1
2022-01-12 16:31:19.907472+0800 BlockTest[1112:462703] test3
从打印结果看出,交换后,新的子类与父类实例都是执行交换后的操作,父类的操作被替换了。
- 使用class_addMethod的情况
2022-01-12 16:33:59.968165+0800 BlockTest[1118:463568] 交换前
2022-01-12 16:33:59.968207+0800 BlockTest[1118:463568] test11
2022-01-12 16:33:59.968240+0800 BlockTest[1118:463568] test3
2022-01-12 16:33:59.968273+0800 BlockTest[1118:463568] --------------
2022-01-12 16:33:59.968309+0800 BlockTest[1118:463568] 交换后
2022-01-12 16:33:59.968441+0800 BlockTest[1118:463568] test1
2022-01-12 16:33:59.968546+0800 BlockTest[1118:463568] test3
2022-01-12 16:33:59.968711+0800 BlockTest[1118:463568] --------------
2022-01-12 16:33:59.968923+0800 BlockTest[1118:463568] 新的子类实例
2022-01-12 16:33:59.969158+0800 BlockTest[1118:463568] test1
2022-01-12 16:33:59.969194+0800 BlockTest[1118:463568] test3
2022-01-12 16:33:59.969226+0800 BlockTest[1118:463568] --------------
2022-01-12 16:33:59.969266+0800 BlockTest[1118:463568] 父类实例
2022-01-12 16:33:59.969343+0800 BlockTest[1118:463568] test11
2022-01-12 16:33:59.969403+0800 BlockTest[1118:463568] test3
从打印结果看出,交换后,新的子类执行的交换后的操作,父类的操作还是交换前的操作。class_addMethod后,将test11的实现添加到了子类中,不影响父类的操作。
总结:
要先尝试添加原 selector 是为了做一层保护,因为如果这个类没有实现原始方法"test11" ,但其父类实现了,那 class_getInstanceMethod 会返回父类的方法。这样 method_exchangeImplementations 替换的是父类的那个方法,这当然不是你想要的。所以我们先尝试添加 test11 ,如果已经存在,再用 method_exchangeImplementations 把原方法的实现跟新的方法实现给交换掉。