Multi Threading GCD

Discussion in 'iOS Programming' started by daproject85, Oct 5, 2012.

  1. daproject85 macrumors member

    Joined:
    Apr 13, 2011
    #1
    Hi Forum,

    I am working on an app which retrieves a photo from the web using a given API. Now without multi-threading as we all know the UI will "hang" until the photoloads. I want to use GCD to let the UI flow while the picture loads. However I am having a hard time grasping how multi-threading works. Here is my code BEFORE multi-threading

    Code:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    	self.photoImageScrollView.delegate = self;
        
        self.photoImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[FlickrFetcher urlForPhoto:self.photoDictionary format:FlickrPhotoFormatLarge]]];
        NSString *a = [self.photoDictionary objectForKey:@"title"];
        self.title = a;
        
        self.photoImageView.contentMode = UIViewContentModeTopLeft;
        self.photoImageScrollView.contentSize = self.photoImageView.image.size;
        self.photoImageView.frame = CGRectMake(0, 0, self.photoImageView.image.size.width, self.photoImageView.image.size.height);
    
        self.photoImageScrollView.minimumZoomScale = .2;
        self.photoImageScrollView.maximumZoomScale = 2.0;
    }
    
    -(void)viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:YES];
        CGFloat defaultZoomScaleX = (self.view.frame.size.width-self.photoImageScrollView.frame.origin.x)/self.photoImageView.image.size.width;
        CGFloat defaultZoomScaleY = (self.view.frame.size.height-self.photoImageScrollView.frame.origin.y)/self.photoImageView.image.size.height;
        self.photoImageScrollView.zoomScale = MAX(defaultZoomScaleX, defaultZoomScaleY);
        
    }
    so i decided to change it like this

    Code:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    	self.photoImageScrollView.delegate = self;
        
        __block NSData *photoData = [[NSData alloc]init];
        
        dispatch_queue_t downloadQueue = dispatch_queue_create("photo downloader", NULL);
        dispatch_async(downloadQueue, ^
                       {
                           photoData = [NSData dataWithContentsOfURL:[FlickrFetcher urlForPhoto:self.photoDictionary format:FlickrPhotoFormatLarge]];
                           dispatch_async(dispatch_get_main_queue(), ^
                                          {
                                              self.photoImageView.image = [UIImage imageWithData:photoData];
                                              
                                              NSString *a = [self.photoDictionary objectForKey:@"title"];
                                              self.title = a;
                                              
                                              self.photoImageView.contentMode = UIViewContentModeTopLeft;
                                              self.photoImageScrollView.contentSize = self.photoImageView.image.size;
                                              self.photoImageView.frame = CGRectMake(0, 0, self.photoImageView.image.size.width, self.photoImageView.image.size.height);
                                              
                                              self.photoImageScrollView.minimumZoomScale = .2;
                                              self.photoImageScrollView.maximumZoomScale = 2.0;
                                              [self viewWillAppear:YES];
                                          });
                       });
        dispatch_release(downloadQueue);
        
        
        
    }
    
    -(void)viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:YES];
        CGFloat defaultZoomScaleX = (self.view.frame.size.width-self.photoImageScrollView.frame.origin.x)/self.photoImageView.image.size.width;
        CGFloat defaultZoomScaleY = (self.view.frame.size.height-self.photoImageScrollView.frame.origin.y)/self.photoImageView.image.size.height;
        self.photoImageScrollView.zoomScale = MAX(defaultZoomScaleX, defaultZoomScaleY);
        
    }
    a couple question i need help with

    1. when the photo is being retrieved, lets say the size is 80MB right
    Code:
    photoData = [NSData dataWithContentsOfURL:[FlickrFetcher urlForPhoto:self.photoDictionary format:FlickrPhotoFormatLarge]];
    we call the main thread so the data is being retrieved in a seperate thread, but right after i dispatch the main queue the next few lines all NEED the image I am getting to set the scroll view size and all. So if these are all running in the main thread how would they know how to set the scroll view and all that stuff?
    [/LIST]
     
  2. Sykte macrumors regular

    Joined:
    Aug 26, 2010
    #2
    You always want to manipulate UIKit "UI stuff" on the main thread and of course the large work should be done on a background thread. So you can do something like this. (Hang with me, I understand you have this)

    Code:
    dispatch_queue_t backgroundQueue;
    backgroundQueue = dispatch_queue_create("app.AppName.Class.QueID", NULL);
    
    dispatch_async(backgroundQueue, ^{
    
       //download image code here.
       //this takes a really long time to download
    
    dispatch_async(dispatch_get_main_queue(), ^{
      //do ui stuff here
      //other changes
    });
    
    });
    
    //load placeholder image here which is loaded from a local resource.
    //misc other code here maybe cell update or other viewDidLoad events.
    
    
    Events in this order

    1. ViewDidLoad is called. <- main thread
    2. background_queue object is created. <- main thread
    3. background_queue is placed on the stack and queued for later. A thread will eventually grab a hold of it and start "downloading your image". Just think of the block as being fired sometime in the near future by an open thread. :) <- still on main thread
    4. The main thread moves forward and loads the placeholder image and other misc code.

    5. Sometime later in a background thread the backgroundQueue's block will begin. The image will begin downloading and once it completes, it will then execute
    Code:
    dispatch_async(dispatch_get_main_queue(), ^{//ui stuff});
    which queues the UI updates on the main thread. The main thread may not update the UI stuff right way for the same reason the background thread wasn't executed right away. Once the main thread is ready your UI stuff is updated.



    Hope this helps.
     
  3. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #3
    Don't use dataWithContentsOfURL. Ever.

    Look at Apple's LazyTableImages.

    GCD is most likely to shoot you in the foot.
     
  4. Sykte macrumors regular

    Joined:
    Aug 26, 2010
    #4

    Huh, sort of generalized statement don't you think. If done correctly GCD is amazing, so I'm not sure what you mean by this.
     
  5. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #5
    Threads are expensive to create. They tie up physical memory for the life of the program.

    Apple's engineers have said in very strong terms that for async downloading, you should use NSURLConnection and create an async download instead.

    Search around the Apple developer boards for posts from Quinn about threading. He has a post "10 reasons why threads are evil!" (or something very much like that.) He's a network engineer, and speaks from a position of athority on the subject.
     
  6. Sykte macrumors regular

    Joined:
    Aug 26, 2010
    #6
    I completely understand that and I never said it wasn't. I was responding to the statement "GCD is most likely to shoot you in the foot.". As stated it was very generalized and should have been clarified. Those particular statements can be very confusing for new comers.
     
  7. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #7
    Gotcha, and good point.

    I would rewrite that statement as

    "Multi-threaded programming is hard, and unless you really know what you are doing, is full of pitfalls and opportunities for subtle, non-deterministic, difficult-to-debug problems. Make sure you read up on concurrent programming and understand race conditions, locks, deadlocks, etc, before attempting multi-threaded development."
     
  8. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #8
    Someone posts "I am having a hard time grasping how multi-threading works." and you think they should be using GCD? Good luck with that.
     
  9. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #9
    GCD is a whole lot easier, safer, and more efficient than POSIX threads or NSThread, and also a little simpler then NSOperation and NSOperationQueue. GCD is a C based API, and based on blocks, so it can be a little hard to grasp for those people who don't know the C underpinnings of Objective C

    The basic rule with multi-threading is if you don't understand it well and have a clear idea of the pitfalls, don't use it.
     
  10. Sykte, Oct 7, 2012
    Last edited: Oct 7, 2012

    Sykte macrumors regular

    Joined:
    Aug 26, 2010
    #10
    Yes, I would advise learning GCD. In this situation GCD may not be the optimal solution however it is a wonderful tool which simplifies threading. With that said I would also recommend reading up on NSThreads and NSOperations.

    I would typically suggest the following.

    1. async api
    2. GCD
    3. NSOperation & NSThread <-- toss up depends on the situation.

    However each situation is a little different and I would also recommend profiling and trying multiple methods.

    I only answered the question posted. It sounded to me he wanted to know more about GCD and the code he had posted.
     
  11. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #11
    That is not true. NSURLConnection supports asynchronous downloads. The system does the background downloading for you, and all your code runs on the main thread. You don't have to write any multi-threaded code at all.

    That is the recommended way to handle background downloading. Using multi-threading is NOT recommended in this case.
     

Share This Page