Though I have already marked an answer as accepted, and offered a bounty at the time, I have since found the true answer I was searching for and realised my question wasn't very clear - primarily because I didn't know what to ask.
How to interact with @autoreleasepool:
When the compiler comes across an @autoreleasepool { }
block, it automatically inserts two function calls. At the opening of the block it inserts the C function call:
void *_objc_autoreleasePoolPush(void);
At the close of the @autoreleasepool
block (including when a return
or break
is encountered within - but NOT when an exception is thrown according to the LLVM docs) the compiler inserts the C function call:
_objc_autoreleasePoolPop(void *ctxt); // ctxt is Apple's label for the param
I believe the void *ctxt
parameter is an indicator of where the current @autoreleasepool
started (really only useful in Apple's implementation - see here). In any case I found it can easily be ignored in custom implementations.
The -autorelease
selector:
Essentially the methodology is this:
- Create (if outermost block) or mark (if inner block) an autorelease pool object (in C or C++) in
_objc_autoreleasePoolPush()
. It seems easiest to me to use a Stack data structure for this but YMMV.
- Ensure this object is in scope within the
-autorelease
selector, or you have a fail-safe way of accessing it.
- Add the object to the autorelease pool object (in whatever way that may be) inside
-autorelease
.
- In
_objc_autoreleasePoolPop(void *)
, send the -release
message to every item in the autorelease pool object until the marker set in _objc_autoreleasePoolPush()
is reached (or you reach the bottom of the pool). Then do any additional required cleanup.
Final notes on threading:
Objective-C @autoreleasepools
are supposed to be thread-local. That is, each thread has its own (if it requires one). As a result, _objc_autoreleasePoolPush()
must have some way of determining whether the current thread already has an active autorelease pool and create the object if it does not.
Similarly, this is why, when creating a thread, the thread must first open an @autoreleasepool { }
block before it does anything else (in Objective-C), and the final thing it must do is close that @autoreleasepool
block (implicitly through break
or return
or with explicit fall-through).
Three final observations:
- If you wish to implement your own autorelease pool object, you will need some form of thread-local storage to store it.
- That last point about threading is why every Objective-C program must begin with an
@autoreleasepool { }
block in main()
.
- This is why Apple ask you to use the
NSThread
class and GCD
rather than DIY threading - they take care of all of this for you!