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.
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.
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.
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.
strong reference (plural strong references) (computing) a reference that does protect the referenced object from collection by a garbage collector.
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. 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];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With