Always pass weak reference of self into block in ARC?

the_critic picture the_critic · Nov 17, 2013 · Viewed 111.8k times · Source

I am a little confused about block usage in Objective-C. I currently use ARC and I have quite a lot of blocks in my app, currently always referring to self instead of its weak reference. May that be the cause of these blocks retaining self and keeping it from being dealloced ? The question is, should I always use a weak reference of self in a block ?

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}

Answer

jemmons picture jemmons · Nov 17, 2013

It helps not to focus on the strong or weak part of the discussion. Instead focus on the cycle part.

A retain cycle is a loop that happens when Object A retains Object B, and Object B retains Object A. In that situation, if either object is released:

  • Object A won't be deallocated because Object B holds a reference to it.
  • But Object B won't ever be deallocated as long as Object A has a reference to it.
  • But Object A will never be deallocated because Object B holds a reference to it.
  • ad infinitum

Thus, those two objects will just hang around in memory for the life of the program even though they should, if everything were working properly, be deallocated.

So, what we're worried about is retain cycles, and there's nothing about blocks in and of themselves that create these cycles. This isn't a problem, for example:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

The block retains self, but self doesn't retain the block. If one or the other is released, no cycle is created and everything gets deallocated as it should.

Where you get into trouble is something like:

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

Now, your object (self) has an explicit strong reference to the block. And the block has an implicit strong reference to self. That's a cycle, and now neither object will be deallocated properly.

Because, in a situation like this, self by definition already has a strong reference to the block, it's usually easiest to resolve by making an explicitly weak reference to self for the block to use:

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

But this should not be the default pattern you follow when dealing with blocks that call self! This should only be used to break what would otherwise be a retain cycle between self and the block. If you were to adopt this pattern everywhere, you'd run the risk of passing a block to something that got executed after self was deallocated.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];