前言:在runtime的常用场景中,Method Swizzling经常会被运用到我们的项目当中。但是你知道这是一把双刃剑,运用不好,会给项目带来很大的伤害。
下面我们就来列举黑魔法Method-Swizzling方法交换的一个坑点
- 交换的目标方法本类未实现,但是父类实现了
@interface Person : NSObject
@end
@implementation Person
- (void)drinkTea {
NSLog(@"class: %@; DrinkTea", NSStringFromClass([self class]));
}
@end
@interface Student : Person
@property (nonatomic, copy) NSString *studentNo;
@end
@implementation Student
+ (void)load {
Method mSwizze = class_getInstanceMethod(self, @selector(drinkTea));
Method mOrigin = class_getInstanceMethod(self, @selector(drinkMilk));
method_exchangeImplementations(mSwizze, mOrigin);
}
- (void)drinkMilk {
NSLog(@"class:%@; StudentNo:%@ drinkMilk", NSStringFromClass([self class]), self.studentNo);
}
@end
Person *p = [[Person alloc] init];
Student *s = [[Student alloc] init];
s.studentNo = @"1";
[p performSelector:@selector(drinkTea)];
[s performSelector:@selector(drinkMilk)];
-[Person studentNo]: unrecognized selector sent to instance 0x600003728240'
父类Person (drinkTea)
子类 Student (drinkMilk)
Student 类去交换 drinkTea和drinkMilk的时候,并不会出现问题。但是当Person类去实现自己的drinkTea的时候,就出现了上面例子的问题
原因分析
Student将自己的drinkMilk和父类Person的drinkTea,进行了调换,当Person调用drinkTea的时候,自然会走到子类的drinkMilk中,一旦这个方法内部出现了父类没有的属性或者方法时,程序就会出现奔溃。
解决方法
将上方的load方法做如下修改
+ (void)load {
Method mSwizze = class_getInstanceMethod(self, @selector(drinkTea));
Method mOrigin = class_getInstanceMethod(self, @selector(drinkMilk));
// 为了防止目标方法本类没有实现,我们先在本类中执行class_addMethod 如果本类已实现sel,返回false,本类未实现sel返回yes
BOOL result = class_addMethod(self, @selector(drinkTea), method_getImplementation(mOrigin), method_getTypeEncoding(mOrigin));
if (result) { // 本类不存在 drinkTea。这个时候 ,因为调用了class_addMethod本类中已经添加了drinkTea的sel了 且已经将drinkTea的sel 指向了drinkMilk的imp,所以 接下来,我们需要将drinkMilk的sel 指向drinkTea的imp
class_replaceMethod([self class],@selector(drinkMilk),
method_getImplementation(mSwizze),
method_getTypeEncoding(mSwizze));
} else { // 本类原本存在drinkTea 那么直接交换
method_exchangeImplementations(mSwizze, mOrigin);
}
}
Person *p = [[Person alloc] init];
Student *s = [[Student alloc] init];
s.studentNo = @"1";
[p performSelector:@selector(drinkTea)];
[s performSelector:@selector(drinkMilk)];
[s performSelector:@selector(drinkTea)];
输出结果:
class: Person; DrinkTea
class: Student; DrinkTea
class: Student; StudentNo:1 drinkMilk
根据上面的运行结果表明已经解决了交换父类方法的情况
网友评论