0

I am new to multithreading in iOS. I need to do three things: get information from the api, parse the information and save to my database. I have these three things in a different files(getAPI,parseAPI and savetoDB). getAPI will call parseAPI and it will in return call savetoDB. I want all three of them to work in background thread.

My question is when I call getAPI, will parseAPI and savetoDB run in the background thread as well? How do I ensure that all three of them run in the background? How do I return the call back to main thread after savetoDB?

Example:

dispatch_queue_t backgroundQueue;
backgroundQueue = dispatch_queue_create("lakesh", NULL);  
- (void)startprocess {    
    dispatch_async(backgroundQueue, ^(void) {
        [self getAPI];
    });    
}

Need some guidance.. Thanks...

lakshmen
  • 28,346
  • 66
  • 178
  • 276

3 Answers3

2

If you issue a function on a background thread, all execution will continue on that thread until it finishes or you call back another function on the main thread. I had worries like you in the beginning, so I made myself the following macros:

/// Stick this in code you want to assert if run on the main UI thread.
#define DONT_BLOCK_UI() \
    NSAssert(![NSThread isMainThread], @"Don't block the UI thread please!")

/// Stick this in code you want to assert if run on a background thread.
#define BLOCK_UI() \
    NSAssert([NSThread isMainThread], @"You aren't running in the UI thread!")

As you can see by the comments, I tend to use these macros at the beginning of methods I want to make sure I'm not using by error in the wrong thread. I've put these macros and more random stuff at https://github.com/gradha/ELHASO-iOS-snippets which you may find useful.

With regards to your question on returning to the main thread, since you are using GCD the best would be to call dispatch_get_main_queue() at the end of your savetoDB with the code you want to run there. If savetoDB is a library function, its entry point should allow passing in the success block you want to run on the main thread when everything finished. This is the pattern used by libraries like https://github.com/AFNetworking/AFNetworking. Note how their examples provide an API where stuff runs in the background and then your code gets called back (usually in the main thread).

Grzegorz Adam Hankiewicz
  • 7,349
  • 1
  • 36
  • 78
1

Yes of course if getAPI calls parseAPI, the code of parseAPI will execute on the same thread than the one getAPI was executed, so in your example on a background queue.

To return the callback to the main thread at the end, use the same techniques as Apple uses with their completionBlock you can see on multiple Apple APIs : simply pass a block (e.g. dispatch_block_t or void(^)(NSError*) or whatever fits your needs) as a parameter to your getAPI: method which will pass it to parseAPI: which will in turn pass it to savetoDB: and at the end savetoDB: can simply use dipatch_async(dispatch_get_main_queue, completionBlock); to call this block of code (passed from method to method) on the main thread.

Note: for your getAPI you can use Apple's sendAsynchronousRequest:queue:completionHandler: method, that will automatically execute the request in the background then call the completion block on the indicated NSOperationQueue (NSOperationQueue uses GCD's dispatch_queue internally). See documentation on NSOperationQueue, GCD and the Concurrency Programming Guide and all the great detailed guide in Apple doc for more info.


-(void)getAPI:( void(^)(NSError*) )completionBlock
{
  NSURLRequest* req = ...
  NSOperationQueue* queue = [[NSOperationQueue alloc] init]; // the completionHandler will execute on this background queue once the network request is done
  [NSURLConnection sendAsynchronousRequest:req queue:queue completionHandler:^(NSURLResponse* resp, NSData* data, NSError* error)
   {
     if (error) {
       // Error occurred, call completionBlock with error on main thread
       dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(error); });
     } else {
       [... parseAPI:data completion:completionBlock];
     }
   }];
}

-(void)parseAPI:(NSData*)dataToParse completion:( void(^)(NSError*) )completionBlock
{
   ... parse datatToParse ...

   if (parsingError) {
     dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(error); });
   } else {
     [... savetoDB:dataToSave completion:completionBlock];
   }
}

-(void)savetoDB:(id)dataToSave completion:( void(^)(NSError*) )completionBlock
{
   ... save to your DB ...

   // Then call the completionBlock on main queue / main thread
   dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(dbError); }); // dbError may be nil if no error occurred of course, that will tell you everything worked fine
}

-(void)test
{
  [... getAPI:^(NSError* err)
   {
      // this code will be called on the main queue (main thread)
      // err will be nil if everythg went OK and vontain the error otherwise
   }];
}
AliSoftware
  • 32,623
  • 6
  • 82
  • 77
  • thanks a lot... after saving to the db, i would like to pass back some variables so that i can take action based on the variable.. How do i do that? – lakshmen Feb 09 '13 at 14:45
  • Exactly the same way I did with the NSError in my example (that's why I added this in my example code actually, to show you how you can pass some variables to your completion block, like the NSError in my example but you can add any other variable to your block to fit your needs). Read de Blocks Programming Guide in Apple doc for more info about that if you're new to blocks. – AliSoftware Feb 09 '13 at 16:35
  • I understand the point in the example you gave.. but I meant I have some local variables in the saveDB for example, which I want to pass back to the main thread.. Can i do this? dispatch_async(dispatch_get_main_queue(), processStatus);? – lakshmen Feb 09 '13 at 16:58
  • I don't understand your difficulty here, it is really the same as in my example… Imagine you need to pass an `NSDictionary` and an `int` at the end of the `saveDB` method, simply add those parameters in the signature of the completion block! E.g. it will become `saveDB:(id)dataToSave completion:( void(^)(NSError*, NSDictionary*, int) )completion` and when you call the completion block you will simply pass around the values you want to return, like `completion(nil, someDict, 5)` for example. Exactly as I already do in my example above with NSError, you do the same for any other value to return – AliSoftware Feb 09 '13 at 19:52
1

Yes, parseAPI and savetoDB will run in the new queue you have created. If you need to modify the UI when the operations are finished, that code must run in the main thread. To do that, get a reference to the main queue and send it some code. For example:

- (void)startprocess {    
    dispatch_async(backgroundQueue, ^(void) {
        [self getAPI];
        dispatch_async(dispatch_get_main_queue(), ^{
             // Refresh the UI with the new information
        });
    });    
}

Don't forget to dispatch_release your new queue when you're done with it.

Another pattern, used by Cocoa itself in many parts of the framework, is to add callback block to the signatures of your API functions that is invoked when the background operation has ended. This Stack Overflow thread explains how to do that.

Community
  • 1
  • 1
Daniel Martín
  • 7,815
  • 1
  • 29
  • 34