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

相关文章
相关标签/搜索