oc runtime 实现交换方法
方案一
method_exchangeImplementations
直接使用method_exchangeImplementations
交换两个方法的实现。
该方案可以实现方法的替换,但是有风险,
就是如果是在子类实现了交换方法,但是交换的是一个父类实现了子类没有实现的方法,就会将父类方法的替换成子类的实现,如果父类的实例对象调用父类的方法的时候,由于已经替换成子类实现的方法,就会因为父类没有实现该方法导致崩溃
如
@interface Father : NSObject
-(void)easeapi;
@end
@implementation Father
-(void)easeapi {
NSLog(@"father easeapi 方法");
//your code
}
@end
@interface Son2 : Father
@end
#import "Son2.h"
#import <objc/runtime.h>
@implementation Son2
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL fromSelector = @selector(easeapi);
SEL toSelector = @selector(new_easeapi);
Method fromMethod = class_getInstanceMethod(class, fromSelector);
Method toMethod = class_getInstanceMethod(class, toSelector);
method_exchangeImplementations(fromMethod, toMethod);
});
}
-(void)new_easeapi {
[self new_easeapi];
NSLog(@"new_easeapi方法");
}
@end
报错如图
由此可见,如果 method_exchangeImplementations 交换的两个方法子类有一个子类没有实现的时候,就会替换父类的方法,风险就此而产生
由此我们可以使用方案二
方案二
代码
#import "Son2.h"
#import <objc/runtime.h>
@implementation Son2
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL fromSelector = @selector(easeapi);
SEL toSelector = @selector(new_easeapi);
Method fromMethod = class_getInstanceMethod(class, fromSelector);
Method toMethod = class_getInstanceMethod(class, toSelector);
if(class_addMethod(class, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
class_replaceMethod(class, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
} else {
method_exchangeImplementations(fromMethod, toMethod);
}
});
}
-(void)new_easeapi {
[self new_easeapi];
NSLog(@"new_easeapi方法");
}
@end
这里使用了 class_addMethod ,class_replaceMethod 和 method_exchangeImplementations 共同完成方法替换
class_addMethod
给指定Class添加一个SEL的实现(或者说是SEL和指定IMP的绑定),添加成功返回YES,SEL已经存在或添加失败返回NO。
它有两个需要注意的点:
如果该SEL在父类中有实现,则会添加一个覆盖父类的方法;
如果该Class中已经有SEL,则返回NO。
执行class_addMethod能避免干扰到父类,这也是为什么推荐大家尽量先使用class_addMethod的原因
class_replaceMethod
如果该Class不存在指定SEL,则class_replaceMethod的作用就和class_addMethod一样;
如果该Class存在指定的SEL,则class_replaceMethod的作用就和method_setImplementation一样。
如图,没有影响到父类
注意事项
如果 父类和子类都没有某个方法的SEL,我们在进行class_addMethod的时候会返回YES,
但是 class_replaceMethod 是失败的
如
@interface Father : NSObject
@end
@implementation Father
@end
@implementation Son2
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL fromSelector = @selector(easeapi);
SEL toSelector = @selector(new_easeapi);
Method fromMethod = class_getInstanceMethod(class, fromSelector);
Method toMethod = class_getInstanceMethod(class, toSelector);
if(class_addMethod(class, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
class_replaceMethod(class, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
} else {
method_exchangeImplementations(fromMethod, toMethod);
}
});
}
-(void)new_easeapi {
NSLog(@"new_easeapi方法");
}
调用 class_addMethod 添加的SEL
效果如图
调用原来存在的SEL
可以看到 执行的方法都是同一个实现,因为添加的SEL 没有实现,所以 class_addMethod 成功了,但是class_replaceMethod 是失败的