第十三条:用“方法调配技术“调试”黑盒方法“
在消息解析时,与给定的选择子名称相对应的方法是不是也可以在运行时改变呢?
没错,就是这样。
若能善用此特性,则可发挥出巨大优势,因为我们既不需要源代码,也不需要通过继承子类来覆写方法就能改变这个类本身的功能。
这样一来,新功能将在本类的所有实例中生效,而不是仅限于覆写了相关方法的那些子类实例。
此方案经常成为“方法调配”(method swizzling)。
类的方法列表会把选择子的名称映射到相关的方法实现之上,使得“动态消息派发系统”能够据此找到应该调用的方法。
这些方法均以函数指针的形式来表示,这种指针叫做IMP,其原型如下:
id (* IMP) (id, SEL, ….)
类中的方法通过映射表把每个选择子都映射到不同的IMP上。
Objective-C运行期系统提供的几个方法都能够用来操作这张表。
开发者可以向其中增加选择子,也可以改变某选择子所对应的方法实现。
还可以交换两个选择子所映射到的指针。
无须修改类,或者编写子类,只要修改了“方法表”的布局,就会反映到程序中所有的实例上。
想交换方法实现,可用下列函数:
void method_exchangeImplementations(Method m1, Method m2)
此函数的两个参数表示待交换的两个方法实现,而方法实现则可以通过下列函数获得:
Method class_getInstanceMethod(Class aClass, SEL aSelector)
此函数根据给定的选择子从类中取出与之相关的方法。
例如:
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplemetations(originalMethod, swappedMethod);
然而在实际应用中,像这样直接交换两个方法的实现的,意义并不大。
通常,我们新编写一个方法,在此方法中实现所需要的附加功能,与原有的方法交换。并调用原有实现。
通过此方法,开发者可以为那些“完全不知道其具体实现的”黑盒方法增加日志记录功能,这非常有利于程序的调试。
不能因为Objective-C有这个特性就一定要用它。若是滥用,反而会令代码变得不易读懂且难于维护。
【要点】
在运行期,可以向类中新增或替换选择子所对应的方法实现。
使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”,开发者通常用此技术向原有视线中添加新功能。
一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。