3

我正在为标准 C API 编写一个 Obj-C 包装器。我想用块替换 C 回调。

让我们想象一个 C API:

void my_async_function(void (* callback)(void *), void *udata);

Obj-C 包装器如下所示:

- (void)myAsyncFunction:(dispatch_block_t)block
{
    void *udata = (__bridge_retained void *)block;
    my_async_function(my_callback, udata);
}

void my_callback(void *udata)
{
    dispatch_block_t block = (__bridge_transfer dispatch_block_t)udata;
    block();
}

__bridge_retained并且__bridge_transfer在许多情况下运行良好,但在块上,它们会导致非常奇怪的行为。

myAsyncFunction 的汇编代码:根本没有保留(Xcode 4.4、ARC、O3)。

非常奇怪的是,以下核心生成了objc_retainBlock我对 myAsyncFunction 的预期:

void *a_global_var;

- (void)myAsyncFunction2:(dispatch_block_t)block
{
    void *udata = (__bridge_retained void *)block;
    a_global_var = udata;
    my_async_function(my_callback, udata);
}

我们可以称其为编译器的错误吗?如果不是,编译器遵循什么规则?

类似主题:

4

1 回答 1

2

尝试:

- (void)myAsyncFunction:(dispatch_block_t)block
{
    void *udata = (__bridge_transfer void *) [block copy];
    my_async_function(my_callback, udata);
}

void my_callback(void *udata)
{
    // however, see the comment in the last paragraph
    dispatch_block_t block = (__bridge_transfer dispatch_block_t)udata;
    block();
}

Block_copy通常,当您将块指针分配给它可以比它引用的块结构寿命更长的位置时,编译器会在调用中进行综合。

但是,编译器无法知道在将 void* 传递给 C api 之后会发生什么,并且无论如何您都在覆盖编译器可能认为它应该与__bridge_retained调用相关的任何内容。在存储引用时保留一个块是不够的。

此外,即使进行了此更改,您的回调也必须只调用一次,因为它负责释放块。如果它永远不会被调用,您将泄漏该块。如果它被多次调用,你就会崩溃。所以你可能想让你的包装类的实例负责管理块的内存,除非 C api 允许你提供一个清理函数,你可以用它来释放块。

于 2012-07-31T12:40:36.567 回答