16

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;
                }
            });
        }
    });
}
iDev
  • 23,310
  • 7
  • 60
  • 85
spring
  • 18,009
  • 15
  • 80
  • 160
  • Is it rendering the image when you put in background thread? – iDev Jan 17 '13 at 00:32
  • @ACB: just did a test - yes, it does render but about 5x slower than calling it from the main thread (as in my example code). – spring Jan 17 '13 at 01:10
  • But as per the UIView documentation, you are supposed to use it in main thread. I had posted that part from documentation in my answer. – iDev Jan 17 '13 at 01:12
  • I would strongly recommend this book, he goes into a lot of detail as to whats going on under the hood, as well as suggesting and working through some ideas for optimisations. http://www.amazon.com/iOS-Core-Animation-Advanced-Techniques-ebook/dp/B00EHJCORC – Sam Clewlow Mar 14 '14 at 16:13

4 Answers4

33

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.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • Weak. Strong. My head is spinning! I think I get what you are saying though. Small followup: in the block for the main queue call I put in an additional conditional to check if a view with the required tag exists. Later I was thinking that is redundant since I check earlier. That's wrong though, yes, because it is possible (if I was doing some subView shuffling) that when the main queue fires it is **later** and separate from the background queue and the first conditional. (newbie thread user here) – spring Jan 17 '13 at 01:04
1

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.

iDev
  • 23,310
  • 7
  • 60
  • 85
1

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.

MaheshShanbhag
  • 1,484
  • 15
  • 14
0

If you will not use strongSelf in the code suggested by Kevin Ballard then it might lead to a crash because of weak getting nilled out.

Also a good practice would be to even check for strong being non nil at the point of creating

strongSelf = weakSelf

   if(strongSelf)
   {
       // do your stuff here 
   }
Shilpi
  • 498
  • 1
  • 6
  • 13