8 Replies
      Latest reply: Oct 12, 2016 3:30 AM by ksigiscar@cmc RSS
      MattDouhan Level 1 Level 1 (0 points)

        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

        • Re: How to avoid this race condition
          rsharp Level 3 Level 3 (145 points)

          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.

            • Re: How to avoid this race condition
              MattDouhan Level 1 Level 1 (0 points)

              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

                • Re: How to avoid this race condition
                  rsharp Level 3 Level 3 (145 points)

                  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.

                  • Re: How to avoid this race condition
                    QuinceyMorris Level 6 Level 6 (2,445 points)

                    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.

                • Re: How to avoid this race condition
                  PBK Level 5 Level 5 (1,650 points)

                  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];
                     });
                  }
                  
                  • Re: How to avoid this race condition
                    eskimo Apple Staff Apple Staff (5,995 points)

                    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"