Home > Articles > Programming > C/C++

New Objective-C Runtime Features in OS X 10.7 and iOS 5

  • Print
  • + Share This
The latest versions of the Objective-C runtimes shipped with iOS, Mac OS X, and even GNUstep provide several new features. David Chisnall looks at a few of these features.
From the author of

Apple's Objective-C runtime acquired several new features in the version shipped with OS X 10.7 and iOS 5. Most of these features are undocumented, although they're present in the headers.

The most obvious change is the addition of Automatic Reference Counting (ARC) support. On OS X 10.6, Apple provides a partial implementation of ARC, but without support for weak references. OS X 10.7 incorporates a much more complex version into the runtime.

Three functions also look quite interesting, with signatures like this:

NS_RETURNS_RETAINED id objc_retainedObject(objc_objectptr_t CF_CONSUMED pointer);

objc_objectptr_t is a typedef for void*, and the implementation of this function does nothing. The ARC optimizer in LLVM will remove any calls that you make to these functions. They're hints to the compiler about pointer ownership. You use this feature to indicate that you've taken ownership of an object pointer. In general, you should use bridging casts instead of calling these functions—they're really useful only if you have some inline functions in headers that need to support ARC and non-ARC modes.

Blocks as Methods

One of my favorite additions—and the one that required the most effort for me to implement in the GNUstep Objective-C runtime—is imp_implementationWithBlock(). This function takes a block and returns an instance method pointer (IMP). The Apple implementation has a hard limit on the number of these pointers that can be allocated.

The block that you pass into this function must take an object as the first argument. The function will then return an IMP for a method that takes the same explicit arguments as the block takes arguments after the object pointer. For example, if you pass a block like this:

^(id, NSUInteger, NSRect)

you'll get back an IMP like this:

*(id, SEL, NSUInteger, NSRect)

This technique works because the block function, like an Objective-C or C++ method, has a hidden argument: a pointer to the block structure. The imp_implementationWithBlock() function returns a trampoline that modifies the first two arguments. When you call the method, the trampoline moves the receiver from the first argument to the second (overwriting the selector) and loads the block pointer into the first argument. It doesn't touch any of the subsequent arguments.

I don't know how Apple is using this functionality, but I strongly suspect that it's related to key-value observing. Being able to swizzle a method with a block can make attaching the observers quite fast.

Memory management is a little bit tricky if you use this function. Blocks are reference counted, but IMPs are not. You can attach an IMP as a method to multiple classes, so the runtime can't automatically release the block when the method is removed. You must do this yourself, tracking the uses of the IMP and calling imp_removeBlock() when the block is removed.

The most irritating thing about this API is that the compiler automatically generates a type encoding for every block and stores it in the block descriptor. When you register the method, you must provide its type encoding. If you use arguments like NSUInteger or NSRect, this encoding can change, depending on whether you're targeting a 32-bit or 64-bit system. Transforming the block encoding into an IMP encoding is trivial, but there's no public API for doing it—or even for accessing the block's type encoding—on OS X. In the GNUstep runtime, I've added a block_copyIMPTypeEncoding_np() function, which returns the type encoding that should be used for the IMP generated from a block.

Using this API is quite simple. This example adds a +count: method that uses a block:

__block int b = 0;
void* blk = ^(id self, int a) {
      b += a;
      return b;
};
blk = Block_copy(blk);
IMP imp = imp_implementationWithBlock(blk);
char *type = block_copyIMPTypeEncoding_np(blk);
assert(NULL != type);
class_addMethod((objc_getMetaClass("Foo")), @selector(count:), imp, type);
free(type);
assert(2 == [Foo count: 2]);
assert(6 == [Foo count: 4]);

In the Apple implementation, you would need to use a hard-coded type-encoding string, "i@:i", instead of the block_copyIMPTypeEncoding_np() function. Not too much extra effort, but you can't write generic functions that do this with a user-provided block (unless they also provide the type encoding). Also, if you change the types of the block, you must remember to change the type encoding.

  • + Share This
  • 🔖 Save To Your Account