Block, weakSelf and strongSelf
Water
|
02/26/2014
|

Block is powerful in Objective-C. But there is a very stupid problem called cyclic retention pitfall, that a block locked an object so that the object will never be release.

For example, in the function:

NSBlockOperation *op = [[[NSBlockOperation alloc] init] autorelease];
[ op addExecutionBlock:^ {
    [self doSomething];
    [self doMoreThing];
} ];
[someOperationQueue addOperation:op];

When the block is created, the compiler will capture all objects that used inside the block and add reference count by 1. In this case, self will be locked and so the object will not be released until the block is finished. Note that cyclic retention pitfall is not only happened in self object, but in 99% case, it happens in self object.

Weakify

To solve the problem, we can simply create a weak reference of the self, and use the weak object instead of the original object inside block. When the weak object is used, the block will not increase the reference count, so self will not be locked. We call to weakify the self.

__weak __typeof__(self) weakSelf = self;
NSBlockOperation *op = [[[NSBlockOperation alloc] init] autorelease];
[ op addExecutionBlock:^ {
    [weakSelf doSomething];
    [weakSelf doMoreThing];
} ];
[someOperationQueue addOperation:op];

When should use weakify

The block will release all objects used inside the blocks scope after the block is finished. If the block is execution type block that will be released after execution is finished, e.g.:

  • GCD dispatch block
  • Most UIKit block

It should be safe not to use weakify technique because the block's lifetime is determined. However, if you are using block that will store block as variable, e.g.:

  • Blocks in NSOperation, AFHTTPRequestOperation
  • Event handler block like something in BlocksKit

Because the lifetime of the block is uncertained, it is suggested to use weakify to prevent cyclic retention pitfall.

Use AFHTTPRequestOperation as an example, if you simple call:

AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFJSONResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
    [self doSomethingUpdateControls];
    NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"Error: %@", error);
}];
[op start];

self will be locked until the network operation finished. It may be okay in most case because it will at most cost several seconds to complete the operation.

But, if you are handling operations using operation queue:

AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFJSONResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
    [self doSomethingUpdateControls];
    NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"Error: %@", error);
}];
[[NSOperationQueue mainQueue] addOperation:op];

There will be chance that the operation will be paused in the queue. When the operation object is not released, it will lock the block scope and so the self object. In this case, if the self object is a viewController and if you don't want to use weakify technique, you must make sure that you have to cancel all operations [[NSOperationQueue mainQueue] removeAllOperations] at some point like [viewDidDisappeared:(BOOL)animated].

Strongify

But there is another problem, because now the self is weakified. Now self will be free and weakSelf can be nil any time. In the above case, the block may able to run doSomething but failed to run doMoreThing because weakSelf may be nullified at that time.

To solve this, we can strongify self before use:

__weak __typeof__(self) weakSelf = self;
NSBlockOperation *op = [[[NSBlockOperation alloc] init] autorelease];
[ op addExecutionBlock:^ {
    __strong __typeof__(self) strongSelf = weakSelf;
    [strongSelf doSomething];
    [strongSelf doMoreThing];
} ];
[someOperationQueue addOperation:op];

So self will not be released inside the block call until the life cycle of the strongSelf ends.

When should use strongify

However, should we use strongSelf every time inside a block?

Yes, it’s safe to use it every time.

No, sometimes you can simply use the weakSelf when:

  • You don't care that self will be nullified in the middle of the block. e.g. setting control’s value. Note that although weakSelf may be released in the middle, it will be nullified and will not crash the code.
  • You are sure that the self will not be released in the middle. e.g. All block are running in main thread.

Syntax Sugar

We can use a third party library libextobjc so that you can some more readable code like this:

@weakify(self);
NSBlockOperation *op = [[[NSBlockOperation alloc] init] autorelease];
[ op addExecutionBlock:^ {
    @strongify(self);   
    [self doSomething];
    [self doMoreThing];
} ];
[someOperationQueue addOperation:op];