Autoreleasing Core Foundation Objects With ARC
Mike Ash tries to use sel_getUid()
to work around not being able to call -autorelease
from ARC code and runs into some interesting issues:
I also said that this code is not harmless. The harm here is exactly that fast autorelease path. To ARC, an
autorelease
in a function or method followed by aretain
in the caller is just a way to pass ownership around. However, that’s not what’s going on in this code. This code is attempting to actually put the object into the autorelease pool no matter what. ARC’s clever optimization ends up bypassing that attempt and as a result, the dictionary is immediately destroyed instead of being placed in the autorelease pool for later destruction.[…]
References to external functions aren’t fully bound when a program is initially loaded. Instead, a stub is generated which has enough information to complete the binding the first time the call is made. On the first call to an external function, the address for that function is looked up, the stub is rewritten to point to it, and then the function call is made. Subsequent calls go directly to the function. By binding lazily, program startup time is improved and time isn’t wasted looking up functions that are never called.
Since CFAutorelease()
requires Mac OS X 10.9, Daniel Jalkut suggests implementing your own function in a file that’s not compiled with ARC:
CFTypeRef MAAutorelease(CFTypeRef CF_RELEASES_ARGUMENT arg) { return [(id)arg autorelease]; }
This is probably the “right” solution, but it’s kind of a pain.
Tammo Freese suggests:
inline CFTypeRef MyAutorelease(CFTypeRef obj) { id __autoreleasing result = CFBridgingRelease(obj); return (__bridge CFTypeRef)result; }
The behavior of the __autoreleasing
ownership qualifier is really subtle, though.
All of this illustrates the leakiness of the ARC abstraction. For simple cases, everything just works, without your having to know how. But at some point you not only have to understand the manual memory management rules that you were avoiding; you also have to understand the calls the compiler is inserting on your behalf and the crazy per-architecture optimizations. With manual memory management, we debugged using tools like ObjectAlloc and Instruments. With ARC, you occasionally need to drop into assembly.