O-C的雞尾酒戲法"Method Swizzling"

偶然在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;

@end

@implementation ClassA(CategoryB)

- (NSString*) myMethod {return @"B"; }

@end

類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)));

}

@end

提示: 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

相關文章
相關標籤/搜索