In this example –that focusses on YapDatabase– we wrap a Objective-C method that takes C-blocks of arbitrary types:
- (id)initWithSetup:(YapDatabaseSecondaryIndexSetup *)setup
block:(YapDatabaseSecondaryIndexBlock)block
blockType:(YapDatabaseSecondaryIndexBlockType)blockType;
The block type is normally specified at runtime with by the blockType
parameter, which is one of the following (specifically the bottom YapDatabaseSecondaryIndexBlockType
list):
typedef id YapDatabaseSecondaryIndexBlock;
typedef void (^YapDatabaseSecondaryIndexWithKeyBlock) \
(NSMutableDictionary *dict,
NSString *collection,
NSString *key);
typedef void (^YapDatabaseSecondaryIndexWithObjectBlock) \
(NSMutableDictionary *dict,
NSString *collection,
NSString *key,
id object);
typedef void (^YapDatabaseSecondaryIndexWithMetadataBlock) \
(NSMutableDictionary *dict,
NSString *collection,
NSString *key,
id metadata);
typedef void (^YapDatabaseSecondaryIndexWithRowBlock) \
(NSMutableDictionary *dict,
NSString *collection,
NSString *key,
id object,
id metadata);
typedef enum {
YapDatabaseSecondaryIndexBlockTypeWithKey = 1031,
YapDatabaseSecondaryIndexBlockTypeWithObject = 1032,
YapDatabaseSecondaryIndexBlockTypeWithMetadata = 1033,
YapDatabaseSecondaryIndexBlockTypeWithRow = 1034
} YapDatabaseSecondaryIndexBlockType;
While arbitrary block types are a great way in C/Objective-C to make one method take blocks with varying signatures, they are problematic for RubyMotion’s compiler because it can’t know which type of block will be used until at runtime. Using this API as-is will lead to a crash, because the Ruby block has not been properly compiled with the expected signature.
What the RubyMotion compiler expects you to pass for the block
parameter is a plain object, as indicated by the YapDatabaseSecondaryIndexBlock
which is an alias for id
, the type that means ‘object of any class’. While a Ruby block is indeed an object, it needs extra metadata about how many arguments to expect, of which type, and the return value type.
To remedy this, we hint the compiler about which type is required by defining a Objective-C ‘category’ (which extends an existing class) and create a method for the specific block type that it takes (in this case YapDatabaseSecondaryIndexWithObjectBlock
). This way, when calling this wrapper method, the compiler will exactly know how to compile the Ruby block and sanity is restored.