Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

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.

like image 878
Snowman Avatar asked Jul 22 '12 19:07

Snowman


People also ask

Why do you generally create a weak reference when using self in a block?

For many of us, it's best practice to always use weak combined with self inside closures to avoid retain cycles. However, this is only needed if self also retains the closure. By adding weak by default you probably end up working with optionals in a lot of cases while it's actually not needed.

What is a weak reference and how could it be useful?

As stated by Java documentation, weak references are most often used to implement canonicalizing mappings. A mapping is called canonicalized if it holds only one instance of a particular value. Rather than creating a new object, it looks up the existing one in the mapping and uses it.

What is strong reference and weak reference in Objective C?

There are two types of object reference: Strong references, which keep an object “alive” in memory. Weak references, which have no effect on the lifetime of a referenced object.

What are strong references?

strong reference (plural strong references) (computing) a reference that does protect the referenced object from collection by a garbage collector.


2 Answers

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.
}];
like image 62
Jonathan Sterling Avatar answered Oct 12 '22 17:10

Jonathan Sterling


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];
}
like image 23
rob mayoff Avatar answered Oct 12 '22 16:10

rob mayoff