How to avoid this race condition

Hi


I have this code in a login view, the user enters username and password and then clicks the login button at which point I run this code


self.DataManager = [[DataManager alloc] fetchTillConfigObject: tillID];
      
[self.navigationController pushViewController:mainMenu animated:YES];


fetchTillConfigObject calls a webservice using NSURLSessionDataTask to authenticate the user and to download a small JSON object that I am saving in NSUserDefaults


My problem is that sometimes if the network is slow the mainMenu gets pushed before the webservice returns an answer.


I need to make sure that the fetchTillConfigObject finishes before the mainMenu gets pushed at all times as the values it downloads is critical for further usage in the application so I have to gurantee it is done before showing the mainMenu


Any guides much appreciated


Matt

Accepted Reply

I need to make sure that the fetchTillConfigObject finishes before the mainMenu gets pushed at all times …

IMO the fundamental issue here is user interface. Given that logging in to the network can take minutes, what do you want the app to show while that’s happening? Typically this involves some sort of activity UI — one that does not support user interaction — that you present until you have enough data to bring up your main UI. You’ll need a solution to this regardless of how you wrangle the underlying asynchrony.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Your code needs to live inside an asynchronous world. I would recommend looking at completion handlers (blocks in Objective-C). Code is then roughly as so (pseudo-code):


Call some asynchronous API and in its completion handler block do the following:

if there is an error, handle that error

else proceed to the next step (e.g. show a new view controller)


The nice thing about blocks is that it keeps all your code in one spot. The older way of doing this was to set up a delegate to handle callbacks. So you'd have one method kick off the async API. Then, one or more delegate APIs to deal with success and failure conditions.

Hi I understand and am using completion handlers already but that does not totally resolve all problems with race conditions unless nested like crazy, I am looking for a cleaner way but am unable to find the solution so far. Ponder the scenario where you need to run n numbers of webserver calls one dependent on the other, having then being subsequently called from the previous ones completion handler bexomes messy very quickly Is there a way to set a flag or something in a completion handler that I could check for before launching the next one ? That way I can break my code into manageable pieces for example dataManager in one file and DB handling in another etc Then from a main file I could call them one by one and wait for the completion handler to finish before calling the next one without having to nest the calls And don't get me wrong I am using async calls without this challenge all over the application but in some use cases you cannot design away the need for synchronous calling of webservices Matt

I think you could use an NSOperationQueue for this. Once all tasks are complete in the queue, you can then move onto the next step.

The easiest way to do this is to write one method for each webserver interaction. Each method will end with an async network operation, and you'll give the operation, as its completion handler, a block that invokes the next method in sequence.


That way, you don't physically nest all the completion handlers in the source code. For example, you might have a method:


- (void) performStep4
{
     …
     [… completionHandler: ^{ [self performStep5] }];
}


etc. You kick off the first method in the sequence, and the rest will follow like dominoes. You'll have to refine this to handle errors and pass any result parameters (to the completion handler) on to the next method if necessary.

Why can't you just use:



-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
   //  handle your JSON object and store it is NSUserDefaults here
    dispatch_async(dispatch_get_main_queue(), ^{
          [self.navigationController pushViewController:mainMenu animated:YES];
   });
}

I need to make sure that the fetchTillConfigObject finishes before the mainMenu gets pushed at all times …

IMO the fundamental issue here is user interface. Given that logging in to the network can take minutes, what do you want the app to show while that’s happening? Typically this involves some sort of activity UI — one that does not support user interaction — that you present until you have enough data to bring up your main UI. You’ll need a solution to this regardless of how you wrangle the underlying asynchrony.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

… one that does not support user interaction …

… except cancellation …

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Check out the Advanced NSOperations WWDC session. They talk about how to architect your application around operations with concurrency and dependencies, which is basically your case. Alternatively, you can use GCD.

While the data loads, you could display a UIActivityIndicatorView as Eskimo suggested.