Search code examples
iosobjective-cmultithreadingobjective-c-blocksmutability

How to handle 3 differents yet simultaneous async calls


Here is my concern: For some reason, I have to retrieve and group the answer coming from 3 different web request results. Once the 3 of them have return, I must then call a completionBlock and pass it all the results. Using NSOperation API, I managed to do something like that :

Session * session = [[ServiceLocator sharedInstance] serviceForName:ServiceLocatorNameUserRepository];


NSString * urlMethod = @"POST";
NSDictionary * params = @{kParkadomWebServiceUser : session.currentUser.userID};

NSMutableDictionary * records = [NSMutableDictionary new];

NSOperation * currentBooking = [self performRequestWithPath:API_BOOKING_INPROGRESS
                                                         method:urlMethod
                                                      parameter:params
                                                     completion:^(id json, NSError * error)
                                                     {
                                                         records[@"current"] = error ? error : json;

                                                     }];

NSOperation * upcomingBookings = [self performRequestWithPath:API_BOOKING_UPCOMING
                                                         method:urlMethod
                                                      parameter:params
                                                     completion:^(id json, NSError * error)
                                                     {
                                                         records[@"upcoming"] = error ? error : json;

                                                     }];

NSOperation * pastBookings = [self performRequestWithPath:API_BOOKING_HISTORY
                                                   method:urlMethod
                                                parameter:params
                                               completion:^(id json, NSError * error)
                                               {
                                                   records[@"past"] = error ? error : json;

                                               }];


NSBlockOperation * completionOperation = [NSBlockOperation blockOperationWithBlock:^{
    completion([records copy], nil);
}];
[completionOperation addDependency:currentBooking];
[completionOperation addDependency:upcomingBookings];
[completionOperation addDependency:pastBookings];

[[NSOperationQueue mainQueue] addOperation:completionOperation];

where completion is obviously the completion block given as an entry parameter. The performRequestWithPath:method:parameter:completion: create an NSOperation, adds it to a global queue and returns it, so I have my 3 web calls here. Then I create a block operation and adds the dependency to the 3 previous block so it is not fired before the 3 blocks did complete.

My concern is over the NSMutableArray thingy. It's seems like a poor design to do something like this and I'm not sure if it's really bug proof in case 2 calls finished exactly at the same time. (note that I made sure in the perform...: method that the completion block is called in the main queue).

Any feedback? Suggestions, criticism, theory... I'm all open :)


Solution

  • You will need to add synchronization.

    @synchronized(myArray) {
      [myArray doSomething];
    }
    

    For example, in your code:

    @synchronized(records) {
        records[@"past"] = error ? error : json;
    }
    

    Edit: @gnasher729 brings up the good point that if they completion blocks are run on the main thread, then there is no need for synchronization.