Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。
与选择子名称相对应的方法是可以在运行期被改变的,所以,我们可以不用通过继承类并覆写方法就能改变这个类本身的功能。
那么如何在运行期改变选择子对应的方法呢? 答:通过操纵类的方法列表的IMP指针
什么是类方法表?什么是IMP指针呢?
类方法表的映射类的方法列表会把选择子的名称映射到相关的方法实现上,使得“动态消息派发系统”能够据此找到应该调用的方法。这些方法均以函数指针的形式来表示,这些指针叫做IMP。例如NSString类的选择子列表:
有了这张表,OC的运行期系统提供的几个方法就能操纵它。开发者可以向其中增加选择子,也可以改变某选择子对应的方法实现,也可以交换两个选择子所映射到的指针以达到交换方法实现的目的。
举个 :交换lowercaseString和uppercaseString方法的实现:
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class],@selector(uppercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
这样一来,类方法表的映射关系就变成了下图:
交换两个方法这时,如果我们调用lowercaseString方法就会实际调用uppercaseString的方法,反之亦然。
然而! 在实际应用中,只交换已经存在的两个方法是没有太大意义的。我们应该利用这个特性来给既有的方法添加新功能:
它的实现原理是:先通过分类增加一个新方法,然后将这个新方法和要增加功能的旧方法替换(旧方法名对应新方法的实现),这样一来,如果我们调用了旧方法,就会实现新方法了。
例如:我们要在原有的lowercaseString方法中添加一条输出语句。
步骤一:我们先将新方法写在NSString的分类里:
@interface NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString;
@end
@implementation NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString {
NSString *lowercase = [self eoc_myLowercaseString];//eoc_myLowercaseString方法会在将来方法调换后执行lowercaseString的方法
NSLog(@"%@ => %@", self, lowercase);//输出语句,便于调试
return lowercase;
}
@end
这段代码看上去好像会陷入递归调用的死循环,但是其实这个方法是准备和lowercaseString方法互换的。所以在runtime里,eco_myLowercaseString选择子实际上对应的是原来的lowercaseString方法实现。
步骤二:交换两个方法的实现(操纵调换IMP指针)
Method originalMethod =
class_getInstanceMethod([NSString class],
@selector(lowercaseString));
Method swappedMethod =
class_getInstanceMethod([NSString class],
@selector(eoc_myLowercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
这样一来,我们如果交换了lowercaseString和eoc_myLowercaseString的方法实现,那么在调用原来的lowercaseString方法后就可以输出新增的语句了。
“NSString *string = @"ThIs iS tHe StRiNg";
NSString *lowercaseString = [string lowercaseString];
// Output: ThIs iS tHe StRiNg => this is the string”
读Effective Objective-C 2.0 有感
网友评论