malkomalko
5/11/2014 - 4:07 PM

README.markdown

#import "YapDatabaseRubyMotion.h"

#if ! __has_feature(objc_arc)
#error This file must be compiled with ARC. Use the `-fobjc-arc` flag.
#endif

@implementation YapDatabaseSecondaryIndex (RubyMotionBlockTypeWrapper)

// Here we define the implementation that does nothing else than forward
// the method call to the normal library’s API. You could say we are
// ‘aliasing’ the method (although we do change the interface).
- (id)initWithSetup:(YapDatabaseSecondaryIndexSetup *)setup
        objectBlock:(YapDatabaseSecondaryIndexWithObjectBlock)block;
{
  return [self initWithSetup:setup
                       block:block
                   blockType:YapDatabaseSecondaryIndexBlockTypeWithObject];
}

@end
#import <YapDatabase/YapDatabaseSecondaryIndex.h>

@interface YapDatabaseSecondaryIndex (RubyMotionBlockTypeWrapper)

// Here we define the prototype of our wrapper method that explicitly
// states what the type signature of the block argument will be.
//
// This will give the RubyMotion compiler enough information to ‘do
// the right thing’.
- (id)initWithSetup:(YapDatabaseSecondaryIndexSetup *)setup
        objectBlock:(YapDatabaseSecondaryIndexWithObjectBlock)block;

@end
app.vendor_project('YapDatabaseRubyMotion', :static, :bridgesupport_cflags => '-I../vendor/Pods/Headers -fobjc-arc', :cflags => '-I../vendor/Pods/Headers -fobjc-arc')

How to wrap Objective-C APIs that take arbitrary block types for RubyMotion.

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.