Cocoa/CoreFoundation Bridging Explained

“Bridging” is a new language feature introduced to Objective-C alongside ARC, that i believe is not very widely understood, at least not fully.

Cocoa always had the ability to “toll-free bridge” between Cocoa objects, such as NSString and their underlying non-object-oriented CoreFoundation equivalents, like CFStringRef. Before ARC, you could just cast back and forth between them as you pleased, but ARC made the rules a bit stricter, and Objective-C introduced new “bridge casts” to formally express these toll-free bridge casts in an ARC-safe manner. Oxygene also supports these casts, with the bridge() compiler magic function.

Questions?

So how do bridge casts work, when do you need them, and what is the difference between the three distinct types? That are questions that i think a lot of people are confused about. I’ll admit i myself wasn’t quite sure what the differences and implications of “bridge” vs. “transfer” vs. “retain” were, until Carlo and i sat down the other day to really formalize how we would properly expose them in Oxygene (and thus had to understand them really well ;)).

As most Objective-C developers who use ARC will know, the answer to the middle question is the easiest. When do you need a bridge cast? Simple: every time you want to go from a Cocoa object to a CoreFoundation entity or back. You know this, because the compiler will tell you by refusing to compile code that was perfectly fine in pre-ARC, such as:

NSString *s = (NSString *)myCFString;

Instead, you now need to write:

NSString *s = (__bridge NSString *)myCFString;

Or do you? The answer is, it depends. As i mentioned before, there are three different types of bridge casts, and it is important to pick the right one (lest you will end up in the land of chasing down memory leaks and weird crashes, once again).

I say there are three types, but you can really think of them as two groups of casts.

“Regular” Bridge Cast

First, there’s what i like to think of as the “regular” bridge cast. In Objective-C that’s expressed with the __bridge keyword; in Oxygene, it’s the default when calling bridge without a BridgeMode enum value (or with BridgeMode.Bridge, the default):

Objective-C

UIColor *u = [UIColor redColor]; CGColorRef c = [u CGColor] id o = (__bridge id)c; [u set]; // or something else that keeps "u" around.

Oxygene

var u := UIColor.redColor; var c := u.CGColor var o := bridge(c);

You can think of this as making the original item (a CGColor) available “on the other side” (as featureless “id”, in this case, as CGColorRef is not toll-free bridged to a Cocoa class), without affecting the original.

The original ownership of the CGColor remains with the UIColor object it was obtained from. The cast object is merely a second view onto the same item, in a different form.

The “regular” bridge cast works both ways — going from a Cocoa to a Core Foundation, or the other way around.

Transfer/Retain Bridge Casts

The second type of casts are the transfer/retain bridge casts casts. I lump them together because they form two sides of the same coin. Different than the regular bridge cast, they do not work in both directions; instead, the transfer bridge cast can be used when going from CoreFoundation to Cocoa, and the retain bridge cast when going the opposite way, from Cocoa to CoreFoundation.

In Objective-C that’s expressed with the __bridge__transfer and __bridge_retained keywords; in Oxygene by calling bridge with a BridgeMode.Transfer or BridgeMode.Retained value.

Why this distinction is necessary will become clear soon, but first let’s look at an example:

Objective-C

CFStringRef c = CFStringCreateWithCString(nil, 'hello', CFStringGetSystemEncoding()); NSString *s = (__bridge_transfer NSString)c; c = nil;

Oxygene

var c := CFStringCreateWithCString(nil, 'hello', CFStringGetSystemEncoding()); var s := bridge(c, BridgeMode.Transfer); c := nil;

As the name implies, the transfer cast transfers the item, and its ownership, to the other side. After the cast from the CFStringRef to an NSString, the NSString (stored in “s”) is the only true owner of the underlying string. The original CFStringRef is no longer relevant, and as you see, we do not (must no) free it.

Essentially, the transfer cast says, “give me a Cocoa object, and then let’s forget the CoreFoundation thing ever existed”.

This is highly useful when writing more concise code. In the example above, we could have used a regular bridge cast, and simply called CFRelease(c) afterwards and everything would have been fine. But imagine the same example written as a single line of code:

Objective-C

NSString *s = (__bridge_transfer NSString)CFStringCreateWithCString(nil, 'hello', CFStringGetSystemEncoding());

Oxygene

var s := bridge(CFStringCreateWithCString(nil, 'hello', CFStringGetSystemEncoding()), BridgeMode.Transfer);

Here we have no reference to the original CFStringRef object, so the transfer cast is essential.

Conversely, you can imagine that using the transfer cast on the CGColor example above would be disastrous. The Original CGColorRef is owned by the UIColor we received it from; we’d have no right to transfer it and invalidate it underneath the UIColor’s feet. Things will crash, eventually. (In fact, a bug report with just this problem that we received from a tester of Oxygene was what promoted me to write this post ;)).

At this point, you might already begin to understand why transfer bridge casts can only work one way in an ARC language and why on the flip side the retain bridge casts is needed:

Objective-C

NSString *s = [NSString stringWithFormat:"hello, %@", name]; CFStringRef c = (__bridge_retained CFStringRef)s; ... CFRelease(c);

Oxygene

var s := NSString.stringWithFormat('hello, %@', name); var c := bridge(s, BridgeMode.Retained); ... CFRelease(c);

When we used transfer bridge casts above, a true transfer happened; the CoreFoundation reference was no longer valid, and we did release it. Going the other way, we do not have this luxury: The original Cocoa object (a string in this case) is managed by ARC, and there is no way to not release it. If a true transfer bridge cast were allowed, ARC would go ahead and over-release an “invalid” object. And that cannot happen.

Instead, the retained bridge cast does the next best thing. It gives us back a CoreFoundation entity we fully own (and have to release), and it leaves the Cocoa object untouched.

You can think of the retain bridge cast as “just like the transfer, but leaving the source object for ARC to deal with”.

When to Use What?

The answer to this question will not always be clear-cut, but a good rule of thumb is to use the regular bridge cast — i.e. __bridge or bridge() w/o mode — when you do not own the original item and/or are merely “borrowing” it to be used on the other side. Use the transfer/retain bridge cast when you do own the original object and want to take ownership of it “on the other side”.

So How Does this Work Under the Hood?

To be honest, you probably don’t really want to know, or at least not think about it, because what the bridge casts do under the hood can be really confusing how you think of them on the higher level.

Let’s start with the transfer bridge cast. It essentially does “nothing”, just as the regular cast would have done before ARC; it converts the pointer type and that’s it, and it does not affect the retain count of the object at all.

Because of how ARC works under the hood, it assumes ownership (or a +1 retain count, if you will) of the Cocoa object pointer and this affectively acts as if the original CoreFoundation object had been released (i.e. gotten a -1 on its retain count). The actual retain count of the item has not changed, but ARC now has staked claim on it.

The regular bridge cast is essentially the same as the transfer, except it does an extra retain on the resulting object when going from Core Foundation to Cocoa. This way, ARC can deal with the object (and eventually release it again). But the underlying CoreFoundation reference is unaffected and keeps its own claim on the entity. When going from Cocoa to CoreFoundation, it does nothing — because ARC is not involved in handling the life cycle of CF objects, it can just pass the original object off as an unowned entity.

Finally, the retain cast works much like the regular bridge, but does an additional retain on the item, before the cast. This way, the original object reference remains retained, and the resulting CF entity is owned (i.e. needs to be freed) as well.

The thing to keep in mind is that there’s a single retain/reference count for each entity. It’s really just a sleight of hand on the compiler’s side how the bridge casts “move” the ownership around between the Cocoa and CoreFoundation references.

But as i said, you probably want to forget about these implementation details, and just think of these bridge casts in the logical terms described above ;).

Summary

I hope you found this article useful and that it helped to provide some insight into the different bridge cast types on the Cocoa platform. Let me know what you think!