使用 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);
...
}