Reputation: 32071
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.
Upvotes: 11
Views: 5038
Reputation: 385660
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. UITableViewCell
s 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];
}
Upvotes: 8
Reputation: 18375
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.
}];
Upvotes: 18