0

I was looking at some code in this thread How do you trigger a block after a delay, like -performSelector:withObject:afterDelay:?. I was wondering, if the block does something asynchronously, when should the block be released?

Let's say I have code that looks like this:

- (void)testMethod:(id)parameter
{
    dispatch_block_t block = ^{
         SomeAsyncTask *t = [SomeAsyncTask withCompletionBlock:^{
                                 [parameter doAction];
                             }];    
    };
    [self performSelector:@selector(executeBlock:)
            onThread:backgroundThread
               withObject:block
            waitUntilDone:NO];
    dispatch_release(block); //I can release the block here because performSelector      retains the block
}

- (void)executeBlock:(id)block
{
    block();
}

Is the key then, that the completion block in SomeASyncTask will retain the parameter so it's safe to release the block?

Community
  • 1
  • 1
JPC
  • 8,096
  • 22
  • 77
  • 110

3 Answers3

2

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.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • Thanks for the extremely detailed response. I've updated my original post to reflect code changes and what I believe is my understanding of what's going on. – JPC Jun 12 '12 at 17:32
1

You can release it immediately after invoking -performSelector:withObject:afterDelay:. (I assume you meant to use the after-delay variant.) As usual, you are responsible for your memory management and other code is responsible for its memory management. Which is another way of saying that -performSelector:withObject:afterDelay: has to retain the receiver and the object that's passed in until after the selector has been performed.

Edited to add: By the way, why wouldn't you use dispatch_after() as illustrated in the answer to the question to which you linked?

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • I would still have the same problem if I released after performSelector. The runBlock method would have to retain the block, and the block would have to release itself. – JPC Jun 08 '12 at 00:16
  • 1
    Why would `-runBlock:` have to retain the block? The main point still stands. Memory management should be done locally. You don't need to adopt a global perspective. Just make sure each part does the right thing and the whole will do the right thing. A "part" is a method or function when talking about a local variable or parameter. It's a class when talking about an instance variable. – Ken Thomases Jun 08 '12 at 00:19
  • The question is I'm not sure what "part" is supposed to do the memory management. Let's say I pass a copy of the block to performSelector, and then release it right below that call...fine, then whoever receives the block has to retain it because it's not synchronous. The block will get dealloc'ed before it's finished – JPC Jun 08 '12 at 00:24
  • 1
    What's not synchronous? The perform-after-delay is not synchronous to the caller (`+doWork`), but after the delay, the actual performance of the selector (`+runBlock:`) is synchronous with the perform-after-delay internals, and those internals are guaranteed to retain the argument until after the selector is performed. It's just not your problem. It's Cocoa's problem to make sure the receiver and the argument are retained until after the selector has been performed. – Ken Thomases Jun 08 '12 at 00:30
  • The work that the block does. I edited my post to make it more clear. The block has some asynchronous work inside of it that has its own completion block. – JPC Jun 08 '12 at 00:34
  • 1
    Changes nothing. First, you edited only your alternative code snippet, which has been unclear all along, since it's syntactically nonsense. If there's a function or method *within* the block that does something asynchronous with *another* block, then **that** is responsible for copying and eventually releasing its block. The block passed to `-performSelector...` will be retained long enough. – Ken Thomases Jun 08 '12 at 00:37
  • But how? I don't understand. If I have a block with a retain count of 1, and that block executes some asynchronous code, how can I possibly release it before the asynchronous code finishes... – JPC Jun 08 '12 at 16:26
  • 1
    A block which *initiates* an asynchronous process is not itself required for the asynchronous process to complete. There's a *separate* block which is passed in as a completion handler. That block will be (must be) copied by the code responsible for carrying out the asynchronous operation. – Ken Thomases Jun 09 '12 at 01:27
  • I suggest you edit your question to put in simple but **real** code that you fear won't work because something is released too early. Then, write up a timeline of when that code calls into methods and when those methods return and where you think the early release would cause a problem. Either that exercise will help you realize there's no problem or we can explain where you've gone wrong. – Ken Thomases Jun 09 '12 at 01:30
-2

I might just try passing an array with the arguments.

JPC
  • 8,096
  • 22
  • 77
  • 110