Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do we have to set __block variable to nil?

From the Transitioning to ARC Release Notes

Use Lifetime Qualifiers to Avoid Strong Reference Cycles

You can use lifetime qualifiers to avoid strong reference cycles. For example, typically if you have a graph of objects arranged in a parent-child hierarchy and parents need to refer to their children and vice versa, then you make the parent-to-child relationship strong and the child-to-parent relationship weak. Other situations may be more subtle, particularly when they involve block objects.

In manual reference counting mode, __block id x; has the effect of not retaining x. In ARC mode, __block id x; defaults to retaining x (just like all other values). To get the manual reference counting mode behavior under ARC, you could use __unsafe_unretained __block id x;. As the name __unsafe_unretained implies, however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use __weak (if you don’t need to support iOS 4 or OS X v10.6), or set the __block value to nil to break the retain cycle.

Okay, so what's different about __block variable?

Why set to nil here? Is __block variable retained twice? Who hold all the reference? The block? The heap? The stack? The thread? The what?

The following code fragment illustrates this issue using a pattern that is sometimes used in manual reference counting.

MyViewController *myController = [[MyViewController alloc] init…];

// ...

myController.completionHandler =  ^(NSInteger result) {
   [myController dismissViewControllerAnimated:YES completion:nil];
};

[self presentViewController:myController animated:YES completion:^{
   [myController release];
}];

As described, instead, you can use a __block qualifier and set the myController variable to nil in the completion handler:

MyViewController * __block myController = [[MyViewController alloc] init…]; //Why use __block. my controller is not changed at all

// ...

myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];

    myController = nil; //Why set to nil here? Is __block variable retained twice? Who hold all the reference? The block? The heap? The stack? The thread? The what?
};

Also why myController is not set to nil by compiler. Why do we have to do so? It seems that the compiler sort of know when myController will no longer be used again namely when the block expire.

like image 395
user4951 Avatar asked Jun 12 '12 13:06

user4951


1 Answers

When you have code of this form:

object.block = ^{
    // reference object from inside the block
    [object someMethodOrProperty];
};

object will retain or copy the block you give to it. But the block itself will also retain object because it is strongly referenced from within the block. This is a retain cycle. Even after the block has finished executing, the reference cycle still exists and neither the object nor the block can be deallocated. Remember that a block can be called multiple times, so it cannot just forget all the variables it references after it has finished executing once.

To break this cycle, you can define object to be a __block variable, which allows you to change its value from inside the block, e.g. changing it to nil to break the cycle:

__block id object = ...;
object.block = ^{
    // reference object from inside the block
    [object someMethodOrProperty];

    object = nil;
    // At this point, the block no longer retains object, so the cycle is broken
};

When we assign object to nil at the end of the block, the block will no longer retain object and the retain cycle is broken. This allows both objects to be deallocated.

One concrete example of this is with with NSOperation's completionBlock property. If you use the completionBlock to access an operation's result, you need to break the retain cycle that is created:

__block NSOperation *op = [self operationForProcessingSomeData];
op.completionBlock = ^{
    // since we strongly reference op here, a retain cycle is created
    [self operationFinishedWithData:op.processedData];

    // break the retain cycle!
    op = nil;
}

As the documentation describes, there are a number of other techniques you can also use to break these retain cycles. For example, you will need to use a different technique in non-ARC code than you would in ARC code.

like image 80
Mike Weller Avatar answered Oct 13 '22 12:10

Mike Weller