Ken Thomases's answer is correct. I want to give a more detailed response to your comments.
First, it is not clear whether you meant performSelector:withObject:
or performSelector:withObject:afterDelay:
, since performSelector:withObject:
is a direct synchronous call, so [self performSelector:@selector(runBlock:) withObject:block_];
is identical to [self runBlock:block_]
. I'll assume it's performSelector:withObject:afterDelay:
, since performSelector:withObject:
is less interesting.
Look at it step by step. performSelector:withObject:afterDelay:
retains its argument, so you can release it after giving the block to it. And performSelector:...
retains it through the performance of its selector. So during the runBlock
, the block is valid because it is still retained by performSelector:...
. During the execution of the block, it is still valid (since it is still inside the execution of runBlock
). doSomethingAsynchronouslyWithCompletionBlock
must retain its argument if it is asynchronous. And so on.
But you don't need to look at it that way. Once you think through it, you will realize that the memory management rules are made so that you don't need to worry about what other code does, only what you need locally.
The memory management rules boil down to the following conditions: Every function / method expects its arguments to be valid when it is called (which usually means through the duration of the function call, since the calling function does not run during this time, so how can it become invalid, unless this function does something which indirectly removes it (like removing from a dictionary)?); and does not have any expectations about how long it will remain valid after the function call. That's it. Everything follows from this.
For example, in your doWork
, you only care about how long you need to use the block. Since you don't need it after performSelector:...
, you can safely release it. It doesn't matter that performSelector:...
might do something with it asynchronously; you may not even know that it is asynchronous (e.g. you could be choosing an unknown method to call dynamically). The point is, what it does doesn't matter. Why? Because the performSelector:...
does not assume the argument to be valid any longer than when you called it. So if it needs to keep it longer (and it does), it must retain it (but you don't need to know this). And it will retain it for as long as it needs it.
Similarly, runBlock
can assume that the argument it was given is valid for the duration of its call. Since it does not need to keep it around for longer (all it does is call the block), it does not need to retain it. The block does not change this. Why? Again, because the block does not assume its arguments (including the block itself) is valid after its call, so runBlock
doesn't need to guarantee it. If the block calls doSomethingAsynchronouslyWithCompletionBlock
, that's fine. doSomethingAsynchronouslyWithCompletionBlock
does not assume anything is valid beyond its call, so if it is truly asynchronous, it must retain it somewhere. etc.