本文为大地瓜原创,欢迎知识共享,转载请注明出处。
虽然你不注明出处我也没什么精力和你计较。
作者微信号:christgreenlaw
本文的原文。本文只对其进行翻译。
iOS 4.3中,有三个非常底层的runtime函数提供了一种新的OC和Blocks之间的桥梁,其目的是加强方法实现的动态生成(facilitating the dynamic generation of method implementations)。
具体一点来说:
IMP imp_implementationWithBlock(void *block);
void *imp_getBlock(IMP anImp);
BOOL imp_removeBlock(IMP anImp);
需要特别指明的是,imp_implementationWithBlock():
接收一个block参数,将其拷贝到堆中,返回一个trampoline(直译为“蹦床”,大家意会吧),可以让block当做OC任何一个类的方法的实现(implementation)--IMP--来使用(一个大前提是block的参数和方法的参数时匹配的)。
int j = 12;
IMP skewIMP = imp_implementationWithBlock(^(id _s, int k) { return k * j; });
这里skewIMP
会包含一个函数指针,可以作为下面这样声明的方法的IMP:
- (int)skew: (int) k;
需要注意的是:imp_implementationWithBlock()
并不返回一个可以像function一样调用的block的函数指针。其中的关键是:IMP总是最少有两个参数:(id self, SEL _cmd)
。
要声明一个block,你要丢弃掉SEL _cmd
,保留其它参数。
就像这样:
-(void)doSomething:
void(*doSomethingIMP)(id s, SEL _c);
void(^doSomethingBLOCK)(id s);
-(void)doSomethingWith:(int)x;
void(*doSomethingWithIMP)(id s, SEL _c, int x);
void(^doSomethingWithBLOCK)(id s, int x);
-(int)doToThis:(NSString*)n withThat:(double)d;
int(*doToThis_withThatIMP)(id s, SEL _c, NSString *n, double d);
int(^doToThis_withThatBLOCK)(id s, NSString *n, double d);
这种模式的做法和OC以及Blocks的ABI(Application Binary Interface,应用程序二进制接口)是有关系的。method其实就是开头带有两个参数的C function:收到消息的object以及正在执行的方法的selector。与之相似的是,调用Block就像开头带有一个参数的C function:一个block的引用(as described in the Block ABI on the llvm.org site)。
在我之前写的 intimate tour of objc_msgSend()中我说过, 想让objc_msgSend运行的更快,有以下几个要求或者优化方式:
- 除非必要,不要碰registers
- 优化tail call
- 不要碰参数列表
imp_implementationWithBlock()
也是一样的,返回的函数指针尽可能少地修改参数列表,然后调用block的实现。因此,它速度快,广泛适用于方法实现。
关键就是,方法实现总是在参数列表起始处有两个指针参数:self & _cmd
。trampoline用第一个参数(self
)重写第二个参数(_cmd
)。将block 的引用放入第一个参数,最后调用block 的实现。
更重要的是,imp_implementationWithBlock()
所返回的函数指针--IMP--和别的IMP没有区别。它可以被传递给任何接受IMP参数的API,传递给class_getMethod()
的 type string 和一个常规的 “编译期IMP”没有什么区别。
没了吗?
block在触发时,自身作为第一个参数,其他的参数,不管有多少,在这个过程中都留着不动。
另外两个函数——imp_getBlock()
和imp_removeBlock()
——是为了完整性而提供的。很显然,移除或销毁一个方法的当前的IMP是应用中快速结束的好方式。
总结一下
Using Xcode 4.0 and iOS 4.3, create a new iOS View Based Application.
Replace the code in the provided main.m with the following:
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
@interface Answerer:NSObject
@end
@interface Answerer(DynamicallyProvidedMethod)
- (int)answerForThis:(int)a andThat:(int)b;
- (void)boogityBoo:(float)c;
@end
@implementation Answerer
@end
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int (^impyBlock)(id, int, int) = ^(id _self, int a, int b)
{
return a+b;
};
// grab an instance of the class we'll modify next
Answerer *a = [Answerer new];
// create an IMP from the block
int (*impyFunct)(id, SEL, int, int) = (void*) imp_implementationWithBlock(impyBlock);
// call the block, call the imp. Note the argumentation differences
NSLog(@"impyBlock: %d + %d = %d", 20, 22, impyBlock(nil, 20, 22));
NSLog(@"impyFunct: %d + %d = %d", 20, 22, impyFunct(nil, NULL, 20, 22));
// dynamically add the method to the class, then invoke it on the previously
// created instance (or we could create the instance after adding, doesn't matter)
class_addMethod([Answerer class], @selector(answerForThis:andThat:), (IMP)impyFunct, "i@:ii");
NSLog(@"Method: %d + %d = %d", 20, 22, [a answerForThis:20 andThat:22]);
// It is just a block; grab some state (the selector & a variable)
SEL _sel = @selector(boogityBoo:);
float k = 5.0;
IMP boo = imp_implementationWithBlock(^(id _self, float c) {
NSLog(@"Executing [%@ -%@%f] %f",
[_self class], NSStringFromSelector(_sel), c,
c * k);
class_addMethod([Answerer class], _sel, boo, "v@:f");
// call the method
[a boogityBoo:3.1415];
// clean up
[a release];
[pool release];
return 0;
}
And the output:
ImpityImp[2298:207] impyBlock: 20 + 22 = 42
ImpityImp[2298:207] impyFunct: 20 + 22 = 42
ImpityImp[2298:207] Method: 20 + 22 = 42
ImpityImp[2298:207] Executing [Answerer -boogityBoo:3.141500] 15.707500