使用 Method Swizzling 扩充方法
Objective-C 运行时允许你在运行时更改方法的实现。这称为方法调配,通常用于交换两种方法的实现。例如,如果交换方法 foo
和 bar
,则发送消息 foo
现在将执行 bar
的实现,反之亦然。
此技术可用于扩充或修补无法直接编辑的现有方法,例如系统提供的类的方法。
在以下示例中,-[NSUserDefaults synchronize]
方法被扩充以打印原始实现的执行时间。
重要提示: 许多人尝试使用 method_exchangeImplementations
进行调配。但是,如果你需要调用要替换的方法,这种方法很危险,因为你将使用与预期接收的不同的选择器来调用它。因此,你的代码可能会以奇怪和意想不到的方式中断 - 特别是如果多方以这种方式调整对象。相反,你应该总是使用 setImplementation
与 C 函数一起调配,允许你用原始选择器调用方法。
#import "NSUserDefaults+Timing.h"
#import <objc/runtime.h> // Needed for method swizzling
static IMP old_synchronize = NULL;
static void new_synchronize(id self, SEL _cmd);
@implementation NSUserDefaults(Timing)
+ (void)load
{
Method originalMethod = class_getInstanceMethod([self class], @selector(synchronize:));
IMP swizzleImp = (IMP)new_synchronize;
old_synchronize = method_setImplementation(originalMethod, swizzleImp);
}
@end
static void new_synchronize(id self, SEL _cmd);
{
NSDate *started;
BOOL returnValue;
started = [NSDate date];
// Call the original implementation, passing the same parameters
// that this function was called with, including the selector.
returnValue = old_synchronize(self, _cmd);
NSLog(@"Writing user defaults took %f seconds.", [[NSDate date] timeIntervalSinceDate:started]);
return returnValue;
}
@end
如果你需要调用一个带参数的方法,你只需将它们作为附加参数添加到函数中。例如:
static IMP old_viewWillAppear_animated = NULL;
static void new_viewWillAppear_animated(id self, SEL _cmd, BOOL animated);
...
Method originalMethod = class_getClassMethod([UIViewController class], @selector(viewWillAppear:));
IMP swizzleImp = (IMP)new_viewWillAppear_animated;
old_viewWillAppear_animated = method_setImplementation(originalMethod, swizzleImp);
...
static void new_viewWillAppear_animated(id self, SEL _cmd, BOOL animated)
{
...
old_viewWillAppear_animated(self, _cmd, animated);
...
}