Cocoa Minute: Objectifying Opaque Core Foundation-Style Structs
Note: I wrote this post in January of 2013. It remained in my Drafts folder till I discovered it today. I have no idea why I didn’t post it back then, but here it is now. -AV
I got stumped last night on an uncommon (to me, anyway) problem while working on an app in Xcode. And because I couldn’t find an answer on Google or Stack Overflow, I figured it belonged online somewhere, so here goes.
Let’s say you have a Core Text Line reference; it’s an opaque data type called CTLineRef that’s essentially a C struct. But you can’t see the contents of that struct. That’s why it’s opaque, silly. This lets Apple tinker with its guts without having to worry about breaking our apps. They do this will all kinds of frameworks: stuff in ApplicationServices and CoreFoundation come to mind.
As a relative Cocoa newbie, I find myself often frustrated by Apple’s tendency to “drop down” into pure C code, but I don’t have the expertise to question them authoritatively on that topic, so I’ll set it aside (for now).
Meanwhile, there’s a real problem I was facing. I wanted to put the CTLineRef into an NSDictionary. However, NSDictionaries can only contain real, honest-to-goodness Objective-C objects. How to stick this opaque struct in there? Some common C types such as Ranges and Rects have convenience methods to turn them into objects:
CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, lineIdx); // self.lineInfoForTap is an NSMutableDictionary [self.lineInfoForTap addObject:@{ @“Range”: NSStringFromRange(NSMakeRange(lineRange.location, lineRange.length)), @“Bounds”: NSStringFromCGRect(lineBounds), @“Line”: // ??? }];
But there’s no such beast for CTLineRef.
After asking on Twitter, my heroic followers responded, and I came up with a couple ways to do this.
Create an NSObject subclass with the CTLineRef as a property
Thanks to Gene Goykhman who suggested this one. It is kind of a pain because you have to create a whole new class to transport a single lousy property. But it works:
@interface INOCTLineObject : NSObject - (id)initWithLine:(CTLineRef)newLine; @property (readwrite, assign) CTLineRef line; @end@implementation INOCTLineObject - (id)initWithLine:(CTLineRef)newLine { if (self = [super init]) { self.line = newLine; } return self; } @end
Then you can create your dictionary entry like so:
@"Line" : [[INOCTLineObject alloc] initWithLine:line]
On the other end, of course, you only have to call the property on the object:
CTLineRef line = [[lineInfo valueForKey:@"Line"] line];
This was the only answer I had, and it worked, though I was unsatisfied with having to create a new object just for this.
Use NSValue
This answer came as I was composing this post! Others had suggested using NSValue, but my initial perusal of the documentation yielded little help: it seemed that you had to supply the properties of the struct in order to “encode” it into an NSValue object. However, Mike Greiner suggested another route: using the pointer value. Here’s how it works:
@"Line" : [NSValue valueWithPointer:line]
and then, when you want the value back:
CTLineRef line = [[lineInfo valueForKey:@"Line"] pointerValue];
This is a lot nicer. Without creating a new object, you get to objectify pretty much any standard C-type struct and lob it around with true Cocoa-like ease.
Have fun!