Alex Stone
Alex Stone

Reputation: 47348

iPhone how to fetch data asynchronously from a web service with API call limit?

I'm pulling data from a web service with an API call limit of 125 per hour. My initial sync of the user's data will use a method similar to the one below. I'm having trouble understanding the correctness of concurrency implications of the code below.

I'm adding a series of AFHTTPRequestOperation over to a serial NSOPerationsQueue (max concurrent count = 1). The resulting calls return asynchronously and cause the method to process the data dictionary. Because of the API call limit, I know that at some point my code will fail and start to return error dictionaries instead.

Can I expect the following code to return full data dictionaries sequentially, or due to asynchronous nature of callbacks, can some of them complete before earlier requests?

Because I'm trying to do initial sync, I want to make sure that once the code fails due to API call limit, I have no "holes" in my data up until the failure point.

-(void)addRequestWithString:(NSString*)requestString
{


    // 1: Create a NSURL and a NSURLRequest to points to the web service providing data. Add Oauth1 information to the request, including any extra parameters that are not in scope of Oauth1 protocol

    NSURL *url = [NSURL URLWithString:requestString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [ self.auth authorizeRequest:request withExtraParams:self.extraAuthParameters];



    // 2: Use AFHTTPRequestOperation class, alloc and init it with the request.
    AFHTTPRequestOperation *datasource_download_operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];

    // 3: Give the user feedback, while downloading the data source by enabling network activity indicator.
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];

    // 4: By using setCompletionBlockWithSuccess:failure:, you can add two blocks: one for the case where the operation finishes successfully, and one for the case where it fails.
    [datasource_download_operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {



        NSDictionary* dictonary = [NSJSONSerialization JSONObjectWithData:(NSData *)responseObject
                                                                  options:NSJSONReadingMutableContainers error:&error];


        [self processResponseDictionary:dictonary];

        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];

    } failure:^(AFHTTPRequestOperation *operation, NSError *error){

        // 8: In case you are not successful, you display a message to notify the user.
        // Connection error message
        DLog(@"API fetch error: %@", error);

        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
    }];

    // 9: Finally, add ìdatasource_download_operationî to ìdownloadQueueî of PendingOperations.
    [[self syncQueue] addOperation:datasource_download_operation];

}

Upvotes: 1

Views: 497

Answers (1)

Aaron Brager
Aaron Brager

Reputation: 66242

Your approach will continue the operations even after they start failing.

If you need the operations to go one at a time, but stop once the failure block is hit, enqueue a new request in the completion block of the prior request.

(This code is from an answer to AFNetworking Synchronous Operation in NSOperationQueue on iPhone; I didn't write it.)

NSEnumerator *enumerator = [operations reverseObjectEnumerator];
AFHTTPRequestOperation *currentOperation = nil;
AFHTTPRequestOperation *nextOperation = [enumerator nextObject]; 
while (nextOperation != nil && (currentOperation = [enumerator nextObject])) {
  currentOperation.completionBlock = ^{
    [client enqueueHTTPRequestOperation:nextOperation];
  }
  nextOperation = currentOperation;
}
[client enqueueHTTPRequestOperation:currentOperation];

If the failure block is hit, the following operations will never be enqueued.

Upvotes: 1

Related Questions