Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update UI on dispatch_get_main_queue()

i have one question related with updating UI on main thread using queues.

Well, suppose we create have a UITableView what shows an UILabel with an UIImageView. The UIImage's are loaded asynchronously in prepareCellfor.. using:

 dispatch_async(t_queue, ^{
   //load image
   //dispatch_async(dispatch_get_main_queue(),^{
     cell.imageView = image;
   }
 });

But while the block is fetching the image the user presses one cell (or the back button on the navigation view controller) an load de DetailViewController for that cell (or goes back in the app).

My question is: what happens when the block launch the main thread to update the imageView for the cell? it's trying to update an UIView that is not loaded on window or even it can have been unload...

Thanks

like image 847
Gerardo Avatar asked Feb 01 '12 23:02

Gerardo


1 Answers

This is a good question. And the answer when using ARC is essentially that the block itself retains the object, so that it will be around later. This is one of those subtle memory gotchas. If the UITableView from which this cell comes is de-allocated and releases all of its cells, this one will be retained (though off-screen) and will have the assignment of cell.imageView = image; completed, then it will be released.

I am a big fan of controlled experiments and set out to test this, but a UITableView has many moving parts (no pun intended). So I created a very simple experiment, using a simple NSObject subclass as follows:

@implementation SayHello
-(void)sayHello{
    NSLog(@"Hello");
}
-(void)dealloc{
    NSLog(@"SayHello dead");
}
@end

Obviously this class is intended to give me a function to call in a block (sayHello) and will produce an NSLog when de-allocated.

I ran my test like this:

SayHello *hello = [[SayHello alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    double delayInSeconds = 30.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        [hello sayHello];
    });
});

The 30 seconds gives even the most lazy runloop time to deallocate the "hello" object (if, in fact, it was not retained). But for those 30 seconds the console is silent. After the 30 seconds have expired I get the "Hello" immediately followed by the "SayHello dead" message.

So how is this a "gotcha"? Well, obviously if you don't realize that Blocks/ARC are doing this, it could end up keeping around things you thought should be gone. But also with your example of a UITableViewCell; what if your cell is displayed once and sends a request over the network for an image, but while the block is waiting for the image the cell is reused? Now there is a second block with reference to that cell trying to set its image. Well, now you have a race in which the loser will decide what image is displayed.

like image 71
NJones Avatar answered Nov 04 '22 20:11

NJones