Weak references inside a block

I’m using an NSOperationQueue and queuing up NSOperationBlocks. Now, blocks have a strong reference to any instances in the block, and the calling object also has a strong hold on the block, so it has been advised to do something like the following:

__weak Cell *weakSelf = self;
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        UIImage *image = /* render some image */
        /* what if by the time I get here self no longer exists? */
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [weakSelf setImageViewImage:image];
        }];
    }];
    [self.renderQueue addOperation:op];

So, my question is, let’s say that by the time the image finishes rendering and that line comes back, the Cell object no longer exists (it has been deallocated, possibly due to cell reuse, which is a bit difficult to formalize). When I go to access [weakSelf setImageViewImage:], will that cause a EXC_BAD_ACCESS error?

  • Modal Dialog Does Not Dismiss Keyboard
  • How to cancel facebook login?
  • Returning data from data-grabbing class from web?
  • Differentiating between initial buy and free “re-buy” in StoreKit/In-App Purchase
  • What is “error in __connection_block_invoke_2: Connection interrupted” in iOS?
  • How to programmatically send SMS on the iPhone without “MFMessageComposeViewController ”?
  • Currently I’m trying to trace what the cause of my problem is, and I’m thinking it might have something to do with this.

    2 Solutions Collect From Internet About “Weak references inside a block”

    So, __weak is a zeroing weak reference. What this means is that during your operation, self may indeed be deallocated, but all weak references to it (namely weakSelf) will be zeroed out. This means that [weakSelf setImageViewImage:image] is just sending a message to nil, which is safe; or, at least, it shouldn’t cause an EXC_BAD_ACCESS. (Incidentally, if you had qualified weakSelf as __unsafe_unretained, you might end up sending messages to a freed object.)

    So, I doubt that sending a message to a __weak reference is causing a crash. If you want to ensure that self survives for the length of your operation, you can get a strong reference to the weak one in the block scope:

    __weak Cell *weakSelf = self;
    
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        Cell *strongSelf = weakSelf; // object pointers are implicitly __strong
        // strongSelf will survive the duration of this operation.
        // carry on.
    }];
    

    If you don’t use weak, you’re creating a retain cycle, but the cycle will be broken as soon as the blocks have finished executing. I probably wouldn’t bother using weak here.

    Anyway, you can send any message to nil, and it will be ignored. So if the weakSelf variable gets set to nil because the Cell object is deallocated, the setImageViewImage: message will silently do nothing. It won’t crash.

    Since you mention cell reuse, I presume your Cell is a subclass of UITableViewCell. In that case, your example code has a serious problem. UITableViewCells normally don’t get deallocated. They get put on the cell reuse queue. So your weakSelf variable will not get zeroed, because a weak reference only get zeroed when the object is actually deallocated.

    By the time the [weakSelf setImageViewImage:image] line gets run, the cell may have been reused to represent a different row in your table, and you’re putting the wrong image in the cell. You should move your image-rendering code out of the Cell class, into your table view’s datasource class:

    - (void)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        Cell *cell = // get a cell...
    
        [self startLoadingImageForIndexPath:indexPath];
        return cell;
    }
    
    - (void)startLoadingImageForIndexPath:(NSIndexPath *)indexPath {
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            UIImage *image = [self renderImageForIndexPath:indexPath];
            dispatch_async(dispatch_get_main_queue(), ^{
                Cell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                [cell setImageViewImage:image];
            });
        }];
        [self.renderQueue addOperation:op];
    }