imp_implementationWithBlock()的實現機制

weixin_34194087發表於2018-01-22

本文為大地瓜原創,歡迎知識共享,轉載請註明出處。
雖然你不註明出處我也沒什麼精力和你計較。
作者微訊號: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

相關文章