Search code examples
iostypedefobjective-c-blocksblock

iOS circular dependencies between block definitions


In my iOS application I want to define two block types that take each other as parameters:

typedef void (^BlockA)(BlockB b);
typedef void (^BlockB)(BlockA a);

This fails compilation with 'Unknown type name BlockB in the first typedef (that makes sense).

I have a workaround which defines the types like this:

typedef void (^BlockA)(id);
typedef void (^BlockB)(BlockA a);

I then cast back to the BlockB type inside the BlockA definition, but at the expense of type safety.

I also looked at not using typedefs, but this results in an infinite nesting of expanded block definitions.

I know how to resolve circular dependencies for structs with forward declarations, but I can't see how to do this with blocks.

If there is no solution to the circular dependency, is there a way that I can restrict the parameter to BlockA to be any Block type rather than the generic id, this would give some level of type safety.


Solution

  • typedef does not define a "real" type. It's basically like a macro that expands out everywhere it's used. That's why typedefs cannot be recursive.

    Another way to think about it is, typedefs are never necessary -- you can always take any piece of code with a typedef, and simply replace every occurrence of it with the underlying type (that's what the compiler does when you compile), and it will always work and be completely equivalent. Think about it -- how would you do it without a typedef? You can't. So you can't do it with a typedef either.

    The only ways to do it are: use id as the argument type to erase the type like you're doing; or, encapsulate the block inside a "real" type like a struct or class. However, if you do it the latter way, you have to explicitly put the block into and extract the block out of the struct or class, which makes the code confusing. Also, struct is dangerous as struct is a scalar C type, and if you need to capture it by a block, it doesn't automatically memory-manage the objects inside the struct. As for class, defining a wrapping class is very verbose, and using it for this causes the allocation of an extraneous dummy object for every block it wraps.

    In my opinion, using id like you're using is fine and is the cleanest way. However, keep in mind that if you need to have that block passed as id be captured by another inner block, that you should cast it back to the block type before being captured, as the capturing semantics is different for block and other object types (blocks are copied, whereas other objects are retained). Just casting it back to the block type at the earliest place will work.