wrap c callbacks by blocks (__bridge_transfer and blocks)

I'm writing an Obj-C wrapper to a standard C API. I would like to replace C callbacks by blocks.

Let imagine a C API:

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

The Obj-C wrapper looks like this:

- (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 y __bridge_transfer work well in many cases but on blocks, they result in a very strange behavior.

The assembly code of myAsyncFunction: has no retain at all (Xcode 4.4, ARC, O3).

What is very strange is that the following core, generates a objc_retainBlock, what I expected for 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);
}

Can we call that a bug of the compiler? If not, what rule the compiler is following?

Temas similares:

preguntado el 31 de julio de 12 a las 11:07

1 Respuestas

Tratar:

- (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();
}

Ordinarily, the compiler synthesizes in a Block_copy call when you assign a block pointer to a location where it could outlive the block structure it references.

However, the compiler has no way of knowing what happens to the void* after you pass it into the C api, and anyway you're overriding whatever the compiler might think it ought to do with the __bridge_retained call. Retaining a block when storing a reference isn't enough.

Also, even with this change, your callback must be called exactly once, as it's responsible for releasing the block. If it never gets called, you'll leak the block. If it gets called more than once, you'll crash. So you'll probably want to make instances of your wrapper class responsible for managing the memory of the block, unless the C api allows you to provide a cleanup function, which you could use to release the block.

Respondido 31 Jul 12, 13:07

Leí este very good article and understand now that block must be copied. As you told, retaining a block when storing a reference isn't enough. So, as you suggested, it is correct to copy it before casting to void *. I'm still wondering why the compiler does not retain it, even if retaining is not enough here. - gabriele mondada

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.