我正在为标准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);
}
我们可以称这是编译器的错误吗? 如果没有,编译器遵循什么规则?
类似主题:
答案 0 :(得分: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();
}
通常,当您将块指针指定给位置where it could outlive the block structure it references时,编译器会在Block_copy
调用中进行合成。
然而,编译器在将它传递给C api之后无法知道void *会发生什么,并且无论如何你都会覆盖编译器认为应该对__bridge_retained
调用应该做的任何事情。 。存储参考时保留一个块是不够的。
此外,即使进行了此更改,您的回调也必须只调用一次,因为它负责释放块。如果它永远不会被调用,你将泄漏块。如果它不止一次被调用,你就会崩溃。所以你可能想让你的包装类的实例负责管理块的内存,除非C api允许你提供一个清理函数,你可以使用它来释放块。