Yes, it's just a retain cycle. Self retains block, block retains self.
The only thing that makes this case confusing is that self only holds the block implicitly (via the notification center instead of any obvious ivar). Otherwise it's a design pattern that every reference counted language user must understand to avoid memory leaks. Weak references to self in blocks are a very common design pattern that all Objective-C programmers should know.
Not to mention that using 'self' in a block is just not a good idea to start with. Conceptually a block is not a method, so it doesn't have a well-defined self. Instead we are relying on the lexical scope capturing of blocks. The thing is, the documentation is very clear about what happens when you access self in a block[1]:
"When a block is copied, it creates strong references to object variables used within the block. If you use a block within the implementation of a method:
If you access an instance variable by reference, a strong reference is made to self;
If you access an instance variable by value, a strong reference is made to the variable."
Right. So if you use self or an instance variable, you've just created a strong reference. In Drew's example, as he is relying on the object going out of scope, that will still leave the reference held by the block, which is itself held by the NotificationCenter. As with all reference cycles, the way out is to get the NotificationCenter to stop observing that notification, or as the Apple documentation says, use a local variable that takes the value of self, and use that local variable inside the block. This is the solution that Drew presents as Attempt6, but quite frankly would have been my very first attempt when the test failed. The stuff about __block is a red-herring, and from the documentation goes in the opposite direction that Drew wants, forcing a strong reference rather than removing it.
Drew also gets bitten by NSAssert using self. I would suggest that it is NSAssert that is doing something dodgy here - it's trying to have an implicit self, as though it was a method, when it most definately isn't a method - and Drew just got bitten by the impedence mismatch. Macros - just say no.
Using blocks is the one bit of Objective-C that still reminds me of the old MRR style. Just like with retain/release/autorelease, it's one thing understanding the pattern, it's another ensuring that your entire program conforms to it. Anywhere that you capture nontrivial objects is a possible failure point.
ReactiveCocoa has some nice patterns for avoiding block callbacks altogether. Eg:
will capture a weak reference to the target, invoke the callback whenever 1 or more events fire and automatically unsubscribe when the target deallocates.