NSNotificationCenter + Blocks: the solution
So since I posted my original article, I’ve heard from a couple of people about a nice solution to the problem, although I’ve then heard from another that it can still bite you in the ass.
Using __block
variables
So the first couple of links above show an approach which takes advantage of the way __block
variables are handled by blocks; Loren even suggested a nice macro-friendly way to set this up:
#define safeSelf(...) do { \
typeof(self) __x = self; \
__block typeof(self) self = __x; \
__VA_ARGS__; \
} while (0)
The above macro creates a scope enclosure which redefines self
as a __block
variable with the same value. The do { ... } while (0)
structure keeps that redefinition’s scope limited to the contents of the code generated by the macro. The use of __VA_ARGS__
means that the passed-in values are actually placed verbatim into here, so you would put code inside the macro invocation like so:
safeSelf(dispatch_async(myQ, ^{[self doSomething];});
Why does __block
help?
When you declare a __block
variable, the compiler actually creates a hidden pointer variable behind the scenes, and always accesses your __block
value by dereferencing that pointer. This way, if/when the block gets copied to the heap, the storage pointed to by this pointer can be moved as well, with the pointer being updated both on the stack and within the block’s variable-containment structure. For instance, this:
__block id me = self;
dispatch_async(myQ, ^{
[me doSomething];
});
…will effectively compile the following:
id * __block_me;
*__block_me = self;
dispatch_async(myQ, ^{
[(*__block_me) doSomething];
});
As a result, the block doesn’t reference contain any variables of type id
, and therefore won’t attempt to retain them. And why won’t it retain the pointed-to variable? Because the purpose of such variables is that their values can change out underneath the block at will, so retaining something isn’t advisable— so it just doesn’t do that at all.
Weak Retains
After the above was mooted, Ken Case of the Omni Group pointed out that the __block
object could be released at any time, leaving a dangling pointer for the block to use. This of course would cause strange behaviour at best, and a crash at worst.
His suggestion was to make use of OmniFoundation’s weak-retain setup. Since that uses some other things inside the OmniFoundation framework, I decided to roll my own pure-Foundation implementation available here.
This has some scary-looking #define
s for pre-filling your conforming implementations, but is reasonably simple in its design:
- Firstly, it provides a weak retain count structure and some helper methods for managing that structure.
- Secondly, it provides simple API endpoints (
-weakRetain
,-weakRelease
, etc.) which will perform a regular retain/release along with using the weak retain count management methods defined in step 1 above. - Lastly, it overrides
-release
to work with the weak retain counts, and has it call through to an implementor-defined function when it determines that the current-release
call would drop the real retain count to the weak retain count, meaning that we should invalidate/release the object.
Note that each weak retain/release operation performs two steps: it actually performs a real retain/release, so that a weak retain will actually keep an object around long enough for us to do our book-keeping. It also changes the weak reference counter up or down. In the implementation file, you’ll notice that -weakRetain
does the real retain first, while -weakRelease
and -weakAutorelease
do the real release last (although remember that the ‘real release’ goes through to our code in this case).
The actual decision to drop the object when only weak references remain happens in AQWeakRetain.m
, starting on line 62:
NSUInteger retainCount = [self retainCount];
BOOL hasWeakRetains = (ivars->count != AQInvalidWeakRetainCount) &&
(ivars->count != 0);
shouldInvalidate = hasWeakRetains && (retainCount - 1 == ivars->count);
- We first grab the real retain count of the object. Cue Bill Bumgarner’s ire1.
- We then look at the weak retain count— if it’s either zero or our ‘invalid’ marker, then there are no weak retains, otherwise, there are weak retains.
- Finally, if there are some extant weak references, we compare that to our real retain count. If the real retain count is one more than the weak retain count, then this release should release the object and ask the implementing class to invalidate all the weak references.
Note that the last part there—asking the implementor to invalidate the weak references—is an implementation detail for your class to take care of.
How does this help me?
The way it works is like this:
- You observe a notification, providing a block as its implementation.
- You then call
[self aq_incrementWeakRetainCount]
. This makes the block’s-retain
behave just like a-weakRetain
. In effect it tells the weak-retain runtime that the previous retain shouldn’t keep the object alive. - When the existing retains on self disappear, the code in our weak-linking runtime’s
-release
implementation will decide that the object should go away, and before it does that it’ll let self inform other objects of this. - We’re doing this all for the benefit of
NSNotificationCenter
, so in our implementation of-invalidateWeakRefcount
we remove our observer, thus removing the block (which is the only thing with a weak reference toself
).
Et voilà.
PS
Many props to the fine folks at the Omni Group for figuring this one out. It clearly pays to have been doing this for a while.
- Bill was lead on the Objective-C runtime for some time, so he knows about this stuff. However, it’s (sadly) the only way we can keep track of the difference between strong & weak retains right now, without just implementing our own retain/release system completely. Which might be on the cards anyway.↩