1

I must be making this harder than it is... or implementing the solutions I see online incorrectly.

I have an array of URLs which I would like to loop through and push the results to a dictionary in order or the array. How can I make it wait for the dictionary to be updated before running the next request? Basically I want to make the calls synchronously in a background thread.

Here is where I call the download:

for (NSString *path in paths) {

    NSURLSession *session = [NSURLSession sessionWithConfiguration [NSURLSessionConfiguration defaultSessionConfiguration]];

    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:path]
                                              cachePolicy:NSURLRequestUseProtocolCachePolicy
                                          timeoutInterval:10];

    NSURLSessionDataTask *task = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error)
                                                {

                                                }
                                                else
                                                {
                                                    NSError *parsingError = nil;
                                                    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
                                                                                                         options:NSJSONReadingAllowFragments
                                                                                                           error:&error];
                                                    if (parsingError)
                                                    {

                                                    }
                                                    else
                                                    {
                                                        [myDictionary addObject:dict];
                                                    }
                                                }
                                            }];
    [task resume];
}

1 Answer 1

2

Unless one request really requires the results of the prior request before being issued (which is not the case here), you should not run them sequentially. It may feel more logical to issue the sequentially, but you pay a huge performance penalty to do so. Issue them concurrently, save the results in some unordered structure (like a dictionary), and then when all done, build your ordered structure.

NSMutableDictionary *results = [NSMutableDictionary dictionaryWithCapacity:[paths count]];

// don't create new session for each request ... initialize this outside of the loop

NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; // or since you're not doing anything special here, use `sharedSession`

// since we're going to block a thread in the process of controlling the degree of 
// concurrency, let's do this on a background queue; we're still blocking
// a GCD worker thread as these run (which isn't ideal), but we're only using
// one worker thread.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // only do for requests at a time, so create queue a semaphore with set count

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(4); // only do four requests at a time

    // let's keep track of when they're all done

    dispatch_group_t group = dispatch_group_create();

    // now let's loop through issuing the requests

    for (NSString *path in paths) {
        dispatch_group_enter(group);                               // tell the group that we're starting another request

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // wait for one of the four slots to open up

        NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:path]
                                                      cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                  timeoutInterval:10];

        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                // ...
            } else {
                NSError *parsingError = nil;
                NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
                if (parsingError) {

                } else {
                    // synchronize updating of dictionary

                    dispatch_async(dispatch_get_main_queue(), ^{
                        results[path] = dict;
                    });
                }
            }

            dispatch_semaphore_signal(semaphore);                  // when done, flag task as complete so one of the waiting ones can start
            dispatch_group_leave(group);                           // tell the group that we're done
        }];
        [task resume];
    }

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // trigger whatever you want when they're all done

        // and if you want them in order, iterate through the paths and pull out the appropriate result in order
        for (NSString *path in paths) {
            // do something with `results[path]`
        }
    });
});

I tried to reduce the amount of extra dependencies here, so I used dispatch groups and semaphores. In the above, I use semaphores to constrain the degree of concurrency and I use dispatch group to identify when it's all done.

Personally, I wouldn't use semaphores and groups, but rather I'd wrap these requests in asynchronous NSOperation subclass, but I was trying to limit the changes I made to your code. But the NSOperation idea is logically the same as the above: Run them concurrently, but constrain the degree of concurrency so you don't end up with them timing out on you, and trigger the retrieval of the results only when all the results are retrieved.

Sign up to request clarification or add additional context in comments.

4 Comments

For instructions on the NSOperationQueue approach, see stackoverflow.com/a/23837970/1271826 or stackoverflow.com/a/24943851/1271826.
Thanks @Rob, I tried to use a semaphore but it wasn't working for me. of course I was trying to do it sequentially... The reason for this is because I wanted them written in the order I issued them. I can order them later as you suggested. I might try the NSOpertion.
Yeah, semaphores can cause problems (notably deadlocks) if not used properly. The above should work fine, though I think asynchronous NSOperation approach is far more elegant.
If loop run 50 times and For ex in loop every time need 5 MB, then loop runs 50 times so this hold (50 times*5MB) = 250 MB memory and I passed chunk with size of 5 MB in each time in loop. so plz guide me I want release memory after first call in loop and then after I want to call next call

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.