Stuck on the Toll-Free Bridge

Something that came up when I tried to create an Objective-C wrapper around CFBinaryHeap was how to deal with Automatic Reference Counting (ARC) when passing void * pointers back and forth across the Core Foundation bridge. Core Foundation types are easy enough to use. The problem wasn’t Core Foundation itself, it was the void * pointers they require.

I needed to get an array of objects from Core Foundation using the function

void CFBinaryHeapGetValues (
   CFBinaryHeapRef heap,
   const void **values
);

which expects an empty array of void * types. Then I have to take the filled-in C array and turn it back into an NSArray. I picked the NSArray method

- (id)initWithObjects:(const id[])objects count:(NSUInteger)count

I kept failing at my attempts get the array of objects across the bridge in a way that satisfied the compiler. My first attempt was to create a C array of type id. That approach gave me an error when I tried to pass it over to a Core Foundation function which expected a pointer to an array of void *

CFIndex size = CFBinaryHeapGetCount(_heap);
id __unsafe_unretained *values = (id __unsafe_unretained *)calloc(size, sizeof(id));
CFBinaryHeapGetValues(_heap, (const void **)values); //error
NSArray *objects = [[NSArray alloc] initWithObjects:values count:size];

The commented line above resulted in this error:

Cast of an indirect pointer to an Objective-C pointer to 'const void **' is disallowed with arc.

My next attempt was to create a C array of CFTypeRef. That got through the CFBinaryHeapGetValues() call okay, but it didn’t get through the call to NSArray.

CFIndex size = CFBinaryHeapGetCount(_heap);
CFTypeRef *cfValues = calloc(size, sizeof(CFTypeRef));    
CFBinaryHeapGetValues(_heap, (const void **)cfValues);    
NSArray *objects = [[NSArray alloc] initWithObjects:(id *)cfValues count:size]; //error

This resulted in the following error:

Pointer to a non-constant type 'id' with no explicit ownership.

Maybe I can make the ownership explicit? I couldn’t figure out any legitimate ownership qualifier. The line

NSArray *objects = [[NSArray alloc] initWithObjects:(id __unsafe_unretained *)cfValues count:size];

resulted in this error:

Cast of a non-Objective-C pointer typ 'CFTypeRef *' (aka 'const void **) to '__unsafe_unretained_id *' is disallowed with ARC.

It was starting to feel like everything was disallowd with ARC. Finally, I realized I was trying to cross the bridge at the wrong point. The solution was to stay in Core Foundation as long as possible. Specifically, use the C array of CFTypeRef to create a CFArray.

CFIndex size = CFBinaryHeapGetCount(_heap);
CFTypeRef *cfValues = calloc(size, sizeof(CFTypeRef));    
CFBinaryHeapGetValues(_heap, (const void **)cfValues);
CFArrayRef objects = CFArrayCreate(kCFAllocatorDefault, cfValues, size, &kCFTypeArrayCallBacks);

I know the array is full of Cocoa objects or CFArayRef objects, so I don’t even need to deal with the messay struct of function pointers. There’s a handy constant kCFTypeArrayCallBacks that provides necessary retain and release functions for the array contents.

Once I have a CFArrayRef, I can get an NSArray with the straightforwrad ARC bridging rules:

NSArray *objArray = (__bridge_transfer NSArray *)objects

And with that, I finally get a file that compiles with ARC.