12

I am working on this code, which does some lengthy asyncronous operation on the net and when it finishes it triggers a completion block where some test is executed and if a variable get a certain value another lengthy operation should start immediately:

-(void) performOperation
{

    void(^completionBlock) (id obj, NSError *err, NSURLRequest *request)= ^(id obj,NSError *err, NSURLRequest *request){


        int variable=0;

        // Do completion operation A
        //...
        //...

        // Do completion operation B                
        //Get the variable value

        if(variable>0){
            [self doLengthyAsynchronousOperationWithCompletionBlock: completionBlock];
        }

    };

//Perform the lenhgty operation with the above completionBlock
    [self doLengthyAsynchronousOperationWithCompletionBlock: completionBlock];

}

-(void) doLengthyAsynchronousOperationWithCompletionBlock: completionBlock
{
    //Do some lengthy asynchronous stuff
}

With this code I get this warning from the compiler:

WARNING: Block pointer variable 'completionBlock' is uninitialized when caputerd by the block

I changed:

void(^completionBlock) (id obj, NSError *err, NSURLRequest *request)= ^(id obj,NSError *err, NSURLRequest *request)

in:

__block void(^completionBlock) (id obj, NSError *err, NSURLRequest *request)= ^(id obj,NSError *err, NSURLRequest *request)

but I get this other warning:

WARNING 2: Capturing 'completionBlock' strongly in this block is likely to lead to a retain cycle

How can I fix this?

Thanks

Nicola

nsgulliver
  • 12,655
  • 23
  • 43
  • 64
nico9T
  • 2,496
  • 2
  • 26
  • 44

1 Answers1

29

WARNING: Block pointer variable 'completionBlock' is uninitialized when captured by the block

This happens because block variables initialized to a recursive block need __block storage.

  • Variables within a block are copied unless declared with __block, in which case they are passed as reference.
  • When a recursive block is assigned to a block variable, the creation happens before the assignment, and such creation triggers a variable copy. Given that the variable hasn't been assigned yet, the copied variable will be a bad value, and it will produce a crash when the block is ran.
  • But if we add __block, the block will be created with a reference to the variable instead. Then the variable will be initialized to the created block, and the block will be ready to use.

WARNING: Capturing 'completionBlock' strongly in this block is likely to lead to a retain cycle

This happens because a block variable is a strong reference to the block, and the block is itself referencing the variable (because as we saw before, the variable has a __block so it is referenced instead copied).

So we need

  • A weak reference to the strong variable inside the block.
  • And a strong reference outside to prevent the block from being deallocated during the scope of the method where it is created.
    void(^ completionBlock) (id obj, NSError *err, NSURLRequest *request);
    void(^ __block __weak weakCompletionBlock) (id obj, NSError *err, NSURLRequest *request);
    weakCompletionBlock = completionBlock = ^(id obj,NSError *err, NSURLRequest *request){
        [self lengthyAsyncMethod:weakCompletionBlock];
    };

The name doLengthyAsynchronousOperationWithCompletionBlock suggests that the method may outlive the method scope where the block is created. Given that the compiler doesn't copy a block passed as an argument, it's responsibility of this method to copy this block. If we are using this block with block aware code (eg: dispatch_async()), this happens automatically.

Had we been assigning this block to an instance variable, we would need a @property(copy) and a weak reference to self inside the block, but this is not the case, so we just use self.

Jano
  • 62,815
  • 21
  • 164
  • 192
  • I tried something like this and it seem to work: I save the block in a copy property of the controller then I pass the property as completionBlock. Nicola – nico9T Mar 26 '13 at 14:29
  • you don't need `__block` on `completionBlock` because it's never used in a block! in fact, it's never used anywhere – newacct Mar 26 '13 at 19:37
  • 1
    also, `self` is not what the OP's warning is about. We don't know whether `self` strongly references the block (the OP does not show us enough code to see that), so it is not safe to assume that it needs to be `__weak` – newacct Mar 26 '13 at 19:39
  • still, `completionBlock` is never used and should be removed. And the `__block` `on weakCompletionBlock` *is* necessary (so removing it is incorrect) since its value is not assigned by the time the block is created. – newacct Mar 27 '13 at 02:13
  • @newacct thanks for your comments, I still need to read more about blocks. However, I don't understand why completionBlock is not needed. It seems to me that it must be present to prevent deallocation of the block before lengthyAsyncMethod has a chance to copy it. – Jano Mar 27 '13 at 15:46
  • @Jano: nevermind, you're right. I forgot that `completionBlock` is used later to pass to `doLengthyAsynchronousOperationWithCompletionBlock:` – newacct Mar 27 '13 at 22:14