OC中的混寫(swizzling)是指透明地把一個方法換成另外一個。簡明的說就是在運行時替換方法。利用方法混寫可以改變那些沒有源代碼的對象(包括系統對象)的行為。
方法混寫的代碼看起來相對比較直觀的,舉個例子說明一下,之前做本地化翻譯的時候就有用到 swizzling 方法。直接去swizze方法 awakeFromNib 然後替換成自己的方法實現以實現本地化翻譯。詳細可以看這篇文章:IOS本地化應用程序。
主要用到的代碼也就這兩句:
+(void)load { // Autoload : swizzle -awakeFromNib with -localizeNibObject as soon as the app (and thus this class) is loaded Method localizeNibObject = class_getInstanceMethod([NSObject class], @selector(localizeNibObject)); Method awakeFromNib = class_getInstanceMethod([NSObject class], @selector(awakeFromNib)); method_exchangeImplementations(awakeFromNib, localizeNibObject); }
-(void)localizeNibObject { //本地化操作.... // Call the original awakeFromNib method [self localizeNibObject]; // this actually calls the original awakeFromNib (and not localizeNibObject) because we did some method swizzling }
如 NSobject + AGi18n 分類代碼:
#import NSObject+AGi18n.h #import@implementation NSObject (AGi18n) //By default do nothing when localizing - (void)localizeFromNib { } #pragma mark - Method swizzling + (void)load { Method awakeFromNibOriginal = class_getInstanceMethod(self, @selector(awakeFromNib)); Method awakeFromNibCustom = class_getInstanceMethod(self, @selector(awakeFromNibCustom)); //Swizzle methods method_exchangeImplementations(awakeFromNibOriginal, awakeFromNibCustom); } - (void)awakeFromNibCustom { //Call standard methods [self awakeFromNibCustom]; //Localize [self localizeFromNib]; } @end
#import UIButton+AGi18n.h @implementation UIButton (AGi18n) - (void)localizeFromNib { //Replace text with localizable version NSArray *states = @[@(UIControlStateNormal), @(UIControlStateHighlighted), @(UIControlStateDisabled), @(UIControlStateSelected), @(UIControlStateApplication)]; for (NSNumber *state in states) { NSString *title = [self titleForState:state.integerValue]; if (title.length > 0) { [self setTitle:[[NSBundle mainBundle] localizedStringForKey:title value:@ table:nil] forState:state.integerValue]; } } } @end
《IOS 7 Programming:Pushing the Limits》一書中對方法混寫舉了一個在NSNotificationCenter中添加觀察者時打印日志的例子。通過上下兩個例子的對比,AGi18n在 swizzling 時還是欠缺部分考慮的。
@interface NSObject(RNSwizzle) +(IMP)swizzleSelector:(SEL)origSelector withIMP:(IMP)newIMP; @end @implementation NSObject(RNSwizzle) +(IMP)swizzleSelector:(SEL)origSelector withIMP:(IMP)newIMP{ Class class = [self class]; Method origMethod = class_getInstanceMethod(class, origSelector); IMP origIMP = method_getImplementation(origMethod); if (!class_addMethod(self, origSelector, newIMP, method_getTypeEncoding(origMethod))) { method_setImplementation(origMethod, newIMP); } return origIMP; } @end接下來看看細節。首先給這個方法傳遞一個選擇器和一個函數指針(IMP)。我們要做的是把該方法的當前實現替換為新實現,並返回舊實現的指針以便以後調用。需要考慮3種情況:類可能直接實現了這個方法,方法可能是類層次結構中的某個父類實現的,或者方法根本沒有實現。如果該類或某個父類實現了這個方法,調用 class_getInstanceMethod 會返回一個 IMP,否則就返回 NULL。
如果方法根本沒有實現,或者是某個父類實現的,那就需要用 class_addMethod 添加方法,這跟通常的覆蓋方法是一樣的。如果 class_addMethod 失敗了,我們就知道了是此類直接實現了正在混寫的方法,那麼就轉而用 method_setImplementation 來把舊實現替換為 新實現。
好了之後,返回原來的 IMP,調用者怎麼用返回值是它自己的事。