2011-06-21 38 views
12

Tôi có một số mã mà về cơ bản nắm này:Block_copy đệ quy?

-(void)doSomethingWithBlock:(BlockTypedef)block 
{ 
    [Foo doSomethingElseWithBlock:^() { 
     block(); 
    }]; 
} 

Foo doSomethingElseWithBlock: cuộc gọi Block_copyBlock_release trên khối mà nó nhận được. Điều này cũng cần thiết ở phạm vi bên ngoài, hoặc sẽ là bên trong Block_copy xử lý việc này?

+2

Bạn hãy thử nó? –

+0

Ứng dụng của tôi không phải là đặc biệt runnable tại thời điểm này, do đó, không có. Tôi đã tìm thấy điều này: http://clang.llvm.org/docs/Block-ABI-Apple.txt Có nội dung "Các khối có thể chứa các biểu thức chữ. Mọi biến được sử dụng trong các khối bên trong được nhập vào tất cả các khối kèm theo phạm vi ngay cả khi các biến không được sử dụng. Điều này bao gồm nhập khẩu const cũng như các biến __block. " Tuy nhiên, trong trường hợp này nó là một khối chữ có chứa một khối, không phải thứ tự được đưa ra ở đó. –

Trả lời

8

tôi trích dẫn hướng dẫn Blocks Programming Topics trên trang web tài liệu nhà phát triển của Apple:

Khi bạn sao chép một khối, bất kỳ tài liệu tham khảo để các khối khác từ bên trong khối được sao chép nếu cần-một toàn bộ cây có thể được sao chép (từ hàng đầu). Nếu bạn có các biến khối và bạn tham chiếu một khối từ bên trong khối, khối đó sẽ được sao chép.

Khi bạn sao chép một khối dựa trên ngăn xếp, bạn sẽ nhận được một khối mới. Nếu bạn sao chép một khối dựa trên khối, tuy nhiên, bạn chỉ cần tăng số lượng giữ lại của khối đó và lấy lại nó làm giá trị trả về của hàm hoặc phương thức sao chép.

2

Bên trong Block_copy() không thực sự có liên quan ở đây. Những gì bạn muốn theo dõi là liệu một khối đã cho tồn tại trên ngăn xếp hay trên vùng heap. Xem xét mã này dựa trên ví dụ của bạn:

@interface Foo : NSObject 
@end 

@implementation Foo 

typedef void(^BlockTypedef)(void); 

+(void)doSomethingElseWithBlock:(BlockTypedef)block 
{ 
    NSLog(@"block=%@", block); 
    BlockTypedef myBlock = Block_copy(block); 
    NSLog(@"myBlock=%@", myBlock); 
    myBlock(); 
    Block_release(myBlock); 
} 

+(void)doSomethingWithBlock:(BlockTypedef)block 
{ 
    [Foo doSomethingElseWithBlock:^() { 
    block(); 
    }]; 
} 
@end 

int main (int argc, const char * argv[]) 
{ 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    int i = 3; 
    BlockTypedef block = ^{ printf("i=%d\n", i); }; 
    NSLog(@"block=%@", block); 
    [Foo doSomethingWithBlock:block]; 
    block(); 
    NSLog(@"block=%@", block); 
    [pool drain]; 
    return 0; 
} 

Điều này sẽ ổn, nhưng blockmyblock là các loại khối khác nhau. block là một khối ngăn xếp và có phạm vi của ngăn xếp cuộc gọi. Nó sẽ tồn tại cho đến khi thoát main(). myblock là một khối malloc (heap), và sẽ tồn tại cho đến khi nó được phát hành. Bạn cần đảm bảo rằng bạn không cố gắng lấy một tham chiếu không được sao chép sang block và sử dụng nó sau khi hoàn tất ngăn xếp. Bạn không thể dán block vào một thanh ivar mà không cần sao chép nó.

Joachim Bengtsson có cách viết tốt nhất về điều này mà tôi biết. @bbum cũng đã viết về nó. (Nếu bbum đi lang thang ở đây và nói rằng tôi là một thằng ngốc về điều này, sau đó nghe anh ta, nhưng tôi nghĩ rằng tôi đang ở đây.)

3

Vâng, điều này là an toàn. Bạn không cần phải sao chép. Tại thời điểm -[Foo doSomethingElseWithBlock:] tạo bản sao khối chữ của bạn, nó sẽ sao chép khối bên trong vào heap.

Tôi đã viết một số mã thử nghiệm để chứng minh với chính mình rằng điều này xảy ra; xem cách printer (chỉ được sử dụng trong block1) được sao chép từ ngăn xếp vào đống tại thời điểm Block_copy(block2) được gọi.

#include <Block.h> 
#include <dispatch/dispatch.h> 
#include <stdio.h> 

typedef void (^void_block)(); 

class ScopedPrinter { 
    public: 
    ScopedPrinter() { 
     printf("construct %p\n", this); 
    } 
    ScopedPrinter(const ScopedPrinter& other) { 
     printf("copy %p <- %p\n", this, &other); 
    } 
    ~ScopedPrinter() { 
     printf("destroy %p\n", this); 
    } 
}; 

void_block invoke(void_block other) { 
    printf("other %p\n", (void*)other); 
    void_block block2 = ^{ 
     printf("other %p\n", (void*)other); 
     other(); 
    }; 
    printf("block2 created\n"); 
    block2 = Block_copy(block2); 
    printf("block2 copied\n"); 
    return block2; 
} 

void_block make_block() { 
    ScopedPrinter printer; 
    printf("printer created\n"); 
    void_block block1 = ^{ 
     printf("block1 %p\n", &printer); 
    }; 
    printf("block1 created\n"); 
    return invoke(block1); 
} 

int main() { 
    void_block block = make_block(); 
    block(); 
    Block_release(block); 
    return 0; 
} 

Transcript:

construct 0x7fff6a23fa70 
printer created 
copy 0x7fff6a23fa50 <- 0x7fff6a23fa70 
block1 created 
other 0x7fff6a23fa30 
block2 created 
copy 0x10a700970 <- 0x7fff6a23fa50 
block2 copied 
destroy 0x7fff6a23fa50 
destroy 0x7fff6a23fa70 
other 0x10a700950 
block1 0x10a700970 
destroy 0x10a700970 
+0

Chỉ cần thêm một điều - ngay cả khi 'make_block()' khai báo 'block1' là' __block void_block block1' (ví dụ, do đó nó có thể đệ quy), bạn vẫn sẽ an toàn.'invoke()' không phải lo lắng về các bộ lưu trữ của các callees của nó. – sfiera