使用 Method Swizzling 擴充方法

Objective-C 執行時允許你在執行時更改方法的實現。這稱為方法調配,通常用於交換兩種方法的實現。例如,如果交換方法 foobar,則傳送訊息 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);

    ...
}