GCD - main vs background thread for updating a UIImageView

24,493

Solution 1

Yes, you need to use the main thread whenever you're touching UIImageView, or any other UIKit class (unless otherwise noted, such as when constructing UIImages on background threads).

One commentary about your current code: you need to assign weakSelf into a strong local variable before using it. Otherwise your conditional could pass, but then weakSelf could be nilled out before you actually try to use it. It would look something like

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    UIImage *tmpImage = [[UIImage alloc] initWithCGImage:asset.defaultRepresentation.fullScreenImage];

    __strong __typeof__(weakSelf) strongSelf = weakSelf;
    if ([strongSelf.scrollView viewWithTag:tagNumber] != nil){

        dispatch_async(dispatch_get_main_queue(), ^{
            __strong __typeof__(weakSelf) strongSelf = weakSelf;
            if ([strongSelf.scrollView viewWithTag:tagNumber]!= nil){
                UIImageView * tmpImageView = (UIImageView*)[strongSelf.scrollView viewWithTag:tagNumber];
                tmpImageView.image = tmpImage;
            }
        });
    }
});

Technically you don't need to do this in the first conditional in the background queue, because you're only dereferencing it once there, but it's always a good idea to store your weak variable into a strong variable before touching it as a matter of course.

Solution 2

Yes you need to use the main thread, since any UI changes needs to be done in the main thread.

As for using the GCD it is used to take the advantage of the Multi Cores on the device. As for the strong and weak self

strong self: you might want a strong self, for in you code

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
(<your class> *) *strongSelf = weakSelf;
    UIImage *tmpImage = [[UIImage alloc] initWithCGImage:asset.defaultRepresentation.fullScreenImage];

    if ([strongSelf.scrollView viewWithTag:tagNumber] != nil){

        dispatch_async(dispatch_get_main_queue(), ^{

            if ([strongSelf.scrollView viewWithTag:tagNumber]!= nil){
                UIImageView * tmpImageView = (UIImageView*)[strongSelf.scrollView viewWithTag:tagNumber];
                tmpImageView.image = tmpImage;
            }
        });
    }
});

say you have a view which make a API call and it takes time, so you switch back to another view but you still want the image to be downloaded then use strong since the block owns the self so the image is downloaded.

weak self: if in the above situation you dont want the image to download once you move to a different view then use weak self, since the block doesn't own any self.

Solution 3

If you need to render the image in UIImageView, you need to do this in main thread. It will not work unless you do it in main queue as shown in your code. Same is the case with any UI rendering.

if ([weakSelf.scrollView viewWithTag:tagNumber]!= nil){
    UIImageView * tmpImageView = (UIImageView*)[weakSelf.scrollView viewWithTag:tagNumber];
    tmpImageView.image = tmpImage;
}

As per Apple documentation,

Threading Considerations: Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call the methods of the UIView class from code running in the main thread of your application. The only time this may not be strictly necessary is when creating the view object itself but all other manipulations should occur on the main thread.

Share:
24,493
spring
Author by

spring

Updated on July 09, 2022

Comments

  • spring
    spring almost 2 years

    I'm new to GCD and blocks and am easing my way into it.

    Background: I'm working on a lazy loading routine for a UIScrollView using the ALAssetsLibrary. When my UIScrollView loads I populate it with the aspectRatioThumbnails of my ALAssets and then as the user scrolls, I call the routine below to load the fullScreenImage of the ALAsset that is currently being displayed. It seems to work.

    (if anyone has a better lazy loading routine please post a comment. I've looked at all I could find plus the WWDC video but they seem to deal more with tiling or have much more complexity than I need)

    My question: I use a background thread to handle loading the fullScreenImage and when that is done I use the main thread to apply it to the UIImageView. Do I need to use the main thread? I've seen that all UIKit updates need to happen on the main thread but I am not sure if that applies to a UIImageView. I was thinking it does, since it is a screen element but then I realized that I simply didn't know.

    - (void)loadFullSizeImageByIndex:(int)index
    {
        int arrayIndex = index;
        int tagNumber = index+1;
        ALAsset *asset = [self.assetsArray objectAtIndex:arrayIndex];
    
        __weak typeof(self) weakSelf = self;
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            UIImage *tmpImage = [[UIImage alloc] initWithCGImage:asset.defaultRepresentation.fullScreenImage];
    
            if ([weakSelf.scrollView viewWithTag:tagNumber] != nil){
    
                dispatch_async(dispatch_get_main_queue(), ^{
    
                    if ([weakSelf.scrollView viewWithTag:tagNumber]!= nil){
                        UIImageView * tmpImageView = (UIImageView*)[weakSelf.scrollView viewWithTag:tagNumber];
                        tmpImageView.image = tmpImage;
                    }
                });
            }
        });
    }