0

Basically I want a way to issue a NSURLRequest multiple times in a loop until a certain condition has been met. I am using a rest api but the rest api only allows up to a maximum of 1,000 results at a time. So if i have, lets say 1,500 total, i want to make a request to get the first 1,000 then i need to get the rest with another almost exact request , except the startAt: parameter is different(so i could go from 1001 - 1500. I want to set this up in a while loop(while i am done loading all the data) and am just reading about semaphores but its not working out like I expected it to. I don't know how many results I have until i make the first request. It could be 50, 1000, or 10,000.

here is the code:

while(!finishedLoadingAllData){
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

        NSURLRequest *myRequest = [self loadData: startAt:startAt maxResults:maxResults];

        [NSURLConnection sendAsynchronousRequest:myRequest
                                           queue:[NSOperationQueue mainQueue]
                               completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {

                                   if(error){
                                       completionHandler(issuesWithProjectData, error);
                                   }
                                   else{
                                       NSDictionary *issuesDictionary = [[NSDictionary alloc] initWithDictionary:[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]];
                                       [issuesWithProjectData addObjectsFromArray:issuesDictionary[@"issues"]];

                                       if(issuesWithProjectData.count == [issuesDictionary[@"total"] integerValue]){
                                           completionHandler([issuesWithProjectData copy], error);
                                            finishedLoadingAllData = YES;
                                       }
                                       else{
                                           startAt = maxResults + 1;
                                           maxResults = maxResults + 1000;
                                       }
                                   }
                                   dispatch_semaphore_signal(semaphore);
                               }];

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    }

Basically I want to keep the while loop waiting until the completion block finished. Then and only then do i want the while loop to check if we have all of the data or not(and if not, make another request with the updated startAt value/maxResults value.

Right now it just hangs on dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

What am i doing wrong or what do i need to do? Maybe semaphores are the wrong solution. thanks.

cspam
  • 2,911
  • 2
  • 23
  • 41
  • 3
    Please don't do this. – nhgrif Jun 04 '15 at 02:37
  • In this case there is only ever 1 block waiting for completion of a NSURL request, you don't need semaphores. If you want to use it try dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); – lead_the_zeppelin Jun 04 '15 at 02:43
  • 2
    "What am i doing wrong". Everything. Do not wait. Do not block. Do not loop. Do not semaphore. Simply understand asynchronous networking and do things the way you're supposed to. – matt Jun 04 '15 at 03:02
  • @matt - so if i cannot loop then i how do I make that request multiple times (unknown number of times)? I have to know when i am finished (thus getting all of the data) before I call my own internal completion block to execute. – cspam Jun 04 '15 at 03:08
  • You could maintain a state variable, and proceed or not in the completion handler to obtain the next piece. This really is no different from any successive downloads situation. – matt Jun 04 '15 at 03:10
  • "obtain the next piece" in my case means starting a new request: NSURLRequest *myRequest = [self loadData: startAt:startAt maxResults:maxResults]; [NSURLConnection sendAsynchronousRequest:myRequest...... I cant just put that in the completion block because I might have 10,000 and might have to make 8 or 9 or 10 or ?? requests until i get all of the data. Without looping I don;t see how I am supposed to get the next piece. – cspam Jun 04 '15 at 03:20

3 Answers3

0

Ok. The more I look, the more I don't think its a bad idea to have semaphores to solve this problem, since the other way would be to have a serial queue, etc. and this solution isn't all that more complicated.
The problem is, you are requesting the completion handler to be run on the main thread

[NSURLConnection sendAsynchronousRequest:myRequest
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) 

and you are probably creating the NSURL request in the main thread. Hence while it waits for the semaphore to be released on the mainthread, the NSURL completion handler is waiting for the mainthread to be free of its current run loop.

So create a new operation queue.

lead_the_zeppelin
  • 2,017
  • 13
  • 23
  • Thanks for the response. When using [NSOperationQueue new] instead of mainQueue it seems to work fine for me. But i see that both nhgrif and matt basically said dont do this. So @lead_the_zeppelin.. thoughts? – cspam Jun 04 '15 at 03:06
  • I wouldn't do an infinite while loop. Semaphores are ok. Just replace the while loop with an NSOperationQueue with a completion handler that calls itself on completion. Something like that – lead_the_zeppelin Jun 04 '15 at 03:48
  • well its not really infinite. Once the finishedLoadingAllData is YES the while loop will exit. Do you mind showing the syntax of how you would use the NSOperationQueue in my example? – cspam Jun 04 '15 at 04:17
0

would it not be easier to do something like this instead:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //run on a background thread

    while(!finishedLoadingAllData){

        NSURLRequest *myRequest = [self loadData: startAt:startAt maxResults:maxResults];
        NSHTTPURLResponse *response = nil;
        NSError *error = nil;

        NSData *responseData = [NSURLConnection sendSynchronousRequest:myRequest returningResponse:&response error:&error]; //blocks until completed

        if(response.statusCode == 200 && responseData != nil){ //handle response and set finishedLoadingAllData when you want
            //do stuff with response
            dispatch_sync(dispatch_get_main_queue(), ^{
                 //do stuff on the main thread that needs to be done
        }
    }
});
Fonix
  • 11,447
  • 3
  • 45
  • 74
0

Please dont do that.. NSURLConnection sendAsynchronousRequest will be loading itself in loop for you, if your data is in chunk.. try this instead..

__block NSMutableData *fragmentData = [NSMutableData data];

[[NSOperationQueue mainQueue] cancelAllOperations];

[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{ 
    [fragmentData appendData:data];

    if ([data length] == 0 && error == nil)
     {
         NSLog(@"No response from server");
     }
     else if (error != nil && error.code == NSURLErrorTimedOut)
     {
         NSLog(@"Request time out");
     }
     else if (error != nil)
     {
         NSLog(@"Unexpected error occur: %@", error.localizedDescription);
     }
     else if ([data length] > 0 && error == nil)
     {
         if ([fragmentData length] == [response expectedContentLength])
         {
             // finished loading all your data
         }
     }
 }];

I've created two chunky json response from server handling method.. And one of them is this, so hope this will be useful to you as well.. Cheers!! ;)

0yeoj
  • 4,500
  • 3
  • 23
  • 41