第13条:用“方法调配技术”调试“黑盒方法”
1. 方法调配技术
既不需要源代码,也不需要通过继承子类来覆写方法就能改变这个类本身的功能。这样一来,新功能将在本类的所有实例中生效,而不是仅限于覆写了相关方法的那些子类实例。此方案经常称为“方法调配”(method swizzling)。
2. 原理
类的方法列表会把选择器的名称映射到相关的方法实现之上,使得“动态消息派发系统”能够据此找到应该调用的方法。这些方法均以函数指针(IMP)的形式来表示,其原型如下:
id (*IMP)(id, SEL, ...)
NSString类的选择器映射表 图例:
Objective-C运行时系统提供的几个方法都能够用来操作这种表。开发者可以向其中新增选择器,也可以改变某选择器所对应的方法实现,还可以交换两个选择器所映射的指针。
- Method class_getInstanceMethod(Class aClass, SEL aSelector) :根据给定的选择器从类中取出与之相关的方法。
- void method_exchangeImplementations( Method m1, Method m2):可以交换两个方法实现
3. 应用案例
编写一个方法,在此方法中实现所需的附加功能,并调用原有实现。
分类 NSString (EOCMyAdditions)头文件:
@interface NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString;
@end
分类 NSString (EOCMyAdditions)实现文件:
@implementation NSString (EOCMyAdditions)
// 新增一个方法,实现附加功能
- (NSString *)eoc_myLowercaseString{
NSString *lowercase = [self eoc_myLowercaseString];
NSLog(@"%@ => %@", self, lowercase);
return lowercase;
}
@end
main函数:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "NSString+EOCMyAdditions.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.交换方法
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(eoc_myLowercaseString));
// 2.交换方法
method_exchangeImplementations(originalMethod, swappedMethod);
// 3.调用lowercaseString方法,但实现的功能却是分类中新增方法的附加功能
NSString *string = @"THIs is tHe StRiNg";
NSString *lowercaseString = [string lowercaseString];
}
return 0;
}
输出结果:
THIs is tHe StRiNg => this is the string
总结:通过此方案,开发者可以为那些“完全不知道其具体实现的”黑盒方法增加日志记录功能,这非常有助于程序调试。然而,此做法只在调试程序时有用,禁止滥用。
要点
- 在运行期,可以向类中新增或替换选择器所对应的方法实现。
- 使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”,开发者常用此技术向原有实现中添加新功能。
- 一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。