This instalment of “A look under ARC’s hood” is all about the new @autoreleasepool
directive. LLVM tells us that the semantics of autorelease pools has changed with LLVM 3.0 and in particular, I thought it might be interesting to see what ARC is doing when it comes to these.
So consider the following method:
1 2 3 4 5 6 |
|
This is entirely contrived, of course, but it should let us see what’s going on. In non-ARC land we would assume here that number
would be allocated inside numberWithInt:
and returned autoreleased. So when the autorelease pool is next drained, it will be released. So let’s see if that’s what happened (as usual, this is ARMv7 instructions):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Well, yes. That’s exactly what’s happening. We can see the call to push an autorelease pool then a call to numberWithInt:
then a call to pop an autorelease pool. Exactly what we’d expect. Now let’s look at the exact same code compiled under ARC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Notice the calls to objc_retainAutoreleasedReturnValue
and objc_release
. What’s happening there is that ARC has determined for us that it doesn’t really need to worry about the autorelease pool that’s in place, because it can simply tell the autorelease to not happen (with the call to objc_retainAutoreleasedReturnValue
) and then release the object later itself. This is desirable as it means the autorelease logic doesn’t have to happen.
Note that the autorelease pool is still required to be pushed and popped because ARC can’t know what’s going on in the calls to numberWithInt:
and NSLog
to know if objects will be put into the pool there. If it did know that they didn’t autorelease anything then it could actually get rid of the push and pop. Perhaps that kind of logic will come in future versions although I’m not quite sure how the semantics of that would work though.
Now let’s consider another example which is where we want to use number
outside of the scope of the autorelease pool block. This should show us why ARC is a wonder to work with. Consider the following code:
1 2 3 4 5 6 7 8 |
|
You might be (correctly) thinking that this is going to cause problems even though it looks perfectly innocuous. It’s a problem because number
will be allocated inside the autorelease pool block, will be deallocated when the autorelease pool pops but is then used after it’s been deallocated. Uh oh! Let’s see if we’re right by compiling it without ARC enabled:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
Obviously no calls to retain, release or autorelease as we’d expect since we haven’t made any explicitly and we’re not using ARC. We can see here that it’s been compiled exactly as we’d expect from our reasoning before. So let’s see what it looks like when ARC gives us a helping hand:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
Round of applause for ARC please! Notice that it’s realised we’re using number
outside of the scope of the autorelease pool block so it’s retained the return value from numberWithInt:
just as it did before, but this time it’s placed the release at the end of the bar
function rather than before the autorelease pool is popped. That will have saved us a crash in some code that we might have thought was correct but actually had a subtle memory management bug.