Toll-Free __bridging

By: Jeremy W. Sherman. Published: . Categories: obj-c core-foundation attributes clang.

Starting with OS X 10.6, you can use the __attribute__ keyword to specify that a Core Foundation property should be treated like an Objective-C object for memory management:

@property(retain) __attribute__((NSObject)) CTFrameRef frame;

This is an easy attribute to miss. It’s also one you can go a long time without finding, because it’s not hard to work around.

(Do note that you can use the NSObject attribute with anything where you’d use CFRetain/CFRelease, not just actual toll-free bridged objects. The toll you’re dodging with __attribute__((NSObject)) is purely syntactic.)

Work Arounds

You can get surprisingly far by just pretending that a CTFrameRef is an id:

@interface MyClass
@property(strong, nonatomic) id frame;  /* CTFrameRef */
@end;

__bridge

You just have to sprinkle casts in the appropriate places:

CTFrameRef frame = CTFrameCreate…
self.frame = (__bridge id)frame;
CFRelease(frame);

__bridge_transfer

You can even use the casts to save you a line of code here and there:

CTFrameRef frame = CTFrameCreate…
self.frame = (__bridge_transfer id)frame;
/* ARC now has ownership of |frame|, so it is responsible for releasing it. */

CFBridgingRelease

Or perhaps use one of the less underscore-y Core Foundation wrappers:

CTFrameRef frame = CTFrameCreate…
self.frame = CFBridgingRelease(frame);
/* Now your Create-rule-trained brain can rest easy, because there’s a balancing Release. */

Macros

But I find the casts clutter up my code, and CF memory management is not bad in small doses, so I used to use macros:

#define $ID (__bridge id)
#define $CF (__bridge void *)

The $CF macro exploits C’s willingless to coerce void * the way $ID expresses id’s willingness to be coerced. That breaks down under Obj-C++, because C++ is not so willing to coerce, so you end up doing something like this instead:

#define $CF(var, obj) lval = ((__bridge __typeof__((var)))(obj))

This ends up working OK, because you tend to assign to a variable of the Core Foundation type, make the cast once there, and then use that CF-typed var throughout the next bit of code:

CTFrameRef frame = $CF(frame, self.frame);
/* do something with |frame| */

Just Use __attribute__((NSObject)) Already!

But I could have saved myself all that mess had I just used __attribute__((NSObject)). Aren’t attributes a wonderful thing?

//cc -g -c -Weverything -Wno-objc-missing-property-synthesis attribute_nsobject.m
/* @file attribute_nsobject.m
 * @author Jeremy W. Sherman
 * @date 2013-01-29
 *
 * Demonstrates the wondrous simplicity of `__attribute__((NSObject))`.
 */
#import <Foundation/Foundation.h>
#import <CoreText/CoreText.h>

@interface MyClass : NSObject
@property(strong, nonatomic) __attribute__((NSObject)) CTFrameRef frame;
@end

/* Look ma, no casts! */
@implementation MyClass
- (void)storeFrame
{
    CTFrameRef frame = NULL;
    self.frame = frame;
}

- (void)loadFrame
{
    CTFrameRef frame __unused = self.frame;
}
@end

ETA: Version Concerns

Justin Spahr-Summers points out via Twitter that this story used not to have such a happy ending. Some member of the clang/objc/ARC juggling act used to fail to retain nonatomic properties. The short tale is documented in a Stack Overflow thread and has been reported as rdar://problem/11040306.

Good news: As of Xcode 4.6 and OS X 10.8.2 (which are what I have on hand to test with), the issue seems to be fixed. The compiler generates a call to objc_setProperty_nonatomic which will objc_retain the new value as expected.

The _nonatomic variant doesn’t seem to exist in my copy of 10.7.1’s objc4-493.9, so from where I’m sitting, this looks to have been fixed in part by an SPI change.

This appears to be an undocumented change affecting only Apple clang as of this time. It also seems that the fix will only work for this property-focused usage pattern; if you need a generic instruction to the compiler to use full ARC semantics for pointers of a certain type, you’ll still have to create a typedef to attach the type info to.

The ARC reference documentation continues to specify that only typedefs can be annotated to create a retainable object pointer type, and the open-source version of clang (as of r173899) still tests for this, and, per the implementation in lib/AST/Type.cpp of Type::isObjCNSObjectType(), this still seems to be the case:

bool Type::isObjCNSObjectType() const {
  if (const TypedefType *typedefType = dyn_cast<TypedefType>(this))
    return typedefType->getDecl()->hasAttr<ObjCNSObjectAttr>();
  return false;
}
bool Type::isObjCRetainableType() const {
  return isObjCObjectPointerType() ||
         isBlockPointerType() ||
         isObjCNSObjectType();
}