偶然在stackoverflow上看到有人問「如何用類別覆蓋一個o-c類的方法,同時在這個方法中調用其原來的實現?」。例如:html
// Base Classide
@interface ClassA : NSObject函數
- (NSString *) myMethod;spa
@end .net
@implementation ClassAcode
- (NSString*) myMethod {return @"A"; }orm
@end htm
//Categoryblog
@interface ClassA (CategoryB)繼承
- (NSString *) myMethod;
@implementation ClassA(CategoryB)
- (NSString*) myMethod {return @"B"; }
類A中有一個方法myMethod,若是用類別B覆蓋類A的myMethod方法,這是很容易作到的。但問題是,我想在類別方法myMethod中,還要調用類A的默認實現,即return@"A";一句,這該怎麼辦?
這是一個頗有趣的問題。實際上,僅僅用O-C的類別機制咱們沒法作到這一點。由於類別的方法覆蓋是徹底覆蓋,若是你在類別中重寫了類方法,則類方法原來的實現就徹底被替代了。
你也許想到了self關鍵字。好比在類別方法中這樣寫:
- (NSString*) myMethod {
return [[self myMethod]stringByAppendingString:@"B"];
}
這會致使一個myMethod方法的循環調用。由於在類別看來,ClassA的myMethod方法已經被類別替換掉了,[self myMethod]調用的仍然是類別的實現,而不是類的實現。
若是使用[super myMethod]呢?類別並非繼承,CategoryB並非ClassA的子類,它仍然是ClassA類。那麼super指向的仍然是ClassA的父類,即NSObject。NSObject中並無myMethod方法,O-C會報一個錯誤。
解決這個問題的最終答案是「方法混合」。即mac osx 10.5之後的新的運行時API「Method Swizzling」。
Swizzling一詞是「攪動、混合」之意,經常使用於雞尾酒製做。因此Method Swizzling實際上是方法混合或方法交換。
咱們先來看一個簡單的例子。這個例子來自於Andras的博客 http://codeshaker.blogspot.com/2012/01/calling-original-overridden-method-from.html:
假設有一個類Test,它有一個方法length:
-(NSUInteger)length{
return 4571;
}
咱們想爲Test定義一個類別,並覆蓋這個方法,但同時,咱們還想調用Test類的默認實現。所以咱們能夠這樣寫:
#import<objc/runtime.h>
@implementation Test(Logging)
- (NSUInteger)logLength {
NSUInteger length = [self logLength];
NSLog(@"Test Logging: %d", length);
return length;
}
+ (void)load {
method_exchangeImplementations(class_getInstanceMethod(self,@selector(length)), class_getInstanceMethod(self, @selector(logLength)));
}
提示: swizzling沒法在類簇上使用。
首先看load方法。在load方法中咱們調用了雞尾酒魔法「method_exchangeImpementations」函數。這個函數將實例方法(在這裏,實際上是類別方法)length和logLength方法的實現進行交換。也就是說,當咱們調用length時,其實是調用logLength,當咱們調用logLength時,其實是調用length。
實際上,一個方法的方法名和方法體是分開的。方法體是花括號 {} 之間的代碼,即方法的 IMP,而在這以前的是方法名,即 SEL。一般狀況下,SEL 是與 IMP 匹配的,但在 swizzling以後,length方法的SEL仍是叫作length,但IMP卻變成了logLength的IMP,logLength的IMP卻變成了length的IMP,以下圖所示。
有了這樣的理解,咱們再來看logLength方法代碼:
第一句,調用實例的logLength方法。因爲「雞尾酒效果」,這其實是調用了length方法的實現,因而咱們獲得了數字4571。
第二句,是logLength方法增長的行爲,即打印數字4571,固然你也能夠改爲任意原來的length方法之外的行爲。
第三句,返回length方法的調用結果。
在main.m方法中,咱們調用Test的length方法(別忘記導入類別的頭文件Test+Log.h);
Test* test=[[Testalloc]init];
[test length];
因爲「雞尾酒效果」,[test length];實際上調用了類別的logLength方法的實現。因而運行結果除了獲得4571的結果外,還會打印出這個數字:
2012-12-14 11:07:04.229SwizzlingTest[49905:207] Test Logging: 4571
我將這個例子的源代碼放在了這裏:
http://download.csdn.net/detail/kmyhy/4886485