5

I want to download 3 files in serial order. Two of them are txt files and one is .gz file. I am using NSURLConnection to download the above files.

I am very new to iOS Programming. I have seen in other question in SO and google that we can use serial dispatch queue to do some operation serially.

But I don't know how to do this with NSURLConnection. I tried below but did not work.

 dispatch_queue_t serialQueue = dispatch_queue_create("com.clc.PropQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
    [self downloadProp];
});
dispatch_async(serialQueue, ^{
    [self downloadDatabase];
});
dispatch_async(serialQueue, ^{
    [self downloadTxt];
});

Above code is not executing connectionDidFinishLoading of NSURLCOnnection. Anyone has Idea how to achieve this?

Anjali
  • 1,623
  • 5
  • 30
  • 50
  • So what you need to do then is to make 3 separate synchronous network requests. –  Jul 13 '15 at 13:59
  • This post shows you how to make the synchronous request: http://stackoverflow.com/a/7262578/4657588 –  Jul 13 '15 at 14:00
  • Hi Anjali, use the AFNetworking framework which uses block based requests. We don't really use NSURLConnection that much anymore its been replaced by NSURLSession, you're working with legacy libraries and code. The others have suggested using synchronous requests, I'd personally just chain some asynchronous requests together to achieve the same effect without blocking the main thread. – Sean Dev Jul 13 '15 at 14:04
  • If you do end up using a synchronous request make sure you DO NOT use it on the main thread otherwise your UI will block and your app could be terminated by the system. – Steve Wilford Jul 13 '15 at 14:05
  • @SteveWilford Good point, well I guess you could do 3 asynchronous requests and have them call the next request when they are finished. –  Jul 13 '15 at 14:23
  • It's fine to do synchronous requests, just make sure to do them all on a background queue. This would most likely be the simplest way. – Steve Wilford Jul 13 '15 at 14:24

3 Answers3

6

NSURLSession provides a queue that will download each task in the order in which they are created.

NSURLSession *session = [NSURLSession sharedSession];

NSURLSessionTask *task1 = [session dataTaskWithURL:[NSURL URLWithString:@"http://yahoo.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    NSLog(@"Complete 1");
}];
NSURLSessionTask *task2 = [session dataTaskWithURL:[NSURL URLWithString:@"http://msn.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    NSLog(@"Complete 2");
}];
NSURLSessionTask *task3 = [session dataTaskWithURL:[NSURL URLWithString:@"http://google.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    NSLog(@"Complete 3");
}];

// Regardless of which order the tasks are "resumed" (aka started) they will execute synchronously in the order added, above.
[task3 resume];
[task1 resume];
[task2 resume];

Update based on comments & chat:

To be more deterministic over the ordering & execution of tasks...

NSURLSession *session = [NSURLSession sharedSession];

__block NSURLSessionTask *task1 = nil;
__block NSURLSessionTask *task2 = nil;
__block NSURLSessionTask *task3 = nil;

task1 = [session dataTaskWithURL:urlToFirstFile completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    // CHECK ERROR
    NSLog(@"First file completed downloading");
    [task2 resume];
}];
task2 = [session dataTaskWithURL:urlToSecondFile completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    // CHECK ERROR
    NSLog(@"Second file completed downloading");
    [task3 resume];
}];
task3 = [session dataTaskWithURL:[NSURL URLWithString:@"http://google.com"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    // CHECK ERROR
    NSLog(@"Third file completed downloading");
}];

[task1 resume];
Steve Wilford
  • 8,894
  • 5
  • 42
  • 66
  • Steve...Can you please help me in implementing the delegates methods of NSURLSession or just point me to the any link which i can refer? As I was using NSURLConnection before....And I have one .gz file which i have to decompress once download is finished. – Anjali Jul 15 '15 at 07:09
  • You wouldn't need to use delegate methods if you use the code in my answer, each block will be called once the contents of the URL has been downloaded. – Steve Wilford Jul 15 '15 at 07:17
  • Have one doubt...Does NSURLSession runs always in background? In my app I want to download the files when I open the app not in bakckground when app is not running. Spinner runs till downloading is happening. – Anjali Jul 15 '15 at 07:20
  • But then how will i know file is downloaded completely as i have to decompress the zipped file also. I have some other conditions like if first files is downloaded then only it should proceed further otherwise stop downloading. – Anjali Jul 15 '15 at 07:23
  • NSURLSession [does not always run](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html#//apple_ref/doc/uid/TP40013509-SW1) in the background: "Data tasks send and receive data using NSData objects. Data tasks are intended for short, often interactive requests from your app to a server. Data tasks can return data to your app one piece at a time after each piece of data is received, or all at once through a completion handler. Because data tasks do not store the data to a file, they are not supported in background sessions". – Steve Wilford Jul 15 '15 at 07:23
  • An easy way to control when to download the next file would be to put the calls to `resume` in the completion block of the previous task. So for example; task 1 complete successfully, call `[task2 resume];`, then task 2 completes successfully, call `[task3 resume];` – Steve Wilford Jul 15 '15 at 07:25
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/83293/discussion-between-anjali-and-steve-wilford). – Anjali Jul 15 '15 at 07:29
  • files are not downloading in specific(in my case serial) order.... is it possible to achieve this using thread or something else....or anything is missing in the above code.. – Anjali Jul 15 '15 at 10:21
  • using the state control of each task with "resume" and "cancel" (cancel with errors and incompleteness of packets [RESTful JSON]) was the ticket. I needed 3 tasks. 2 depended on a successful 1 and 3 best after 2 .... Using the state control mechanisms to utilize NSURLSession is a viable customized solution to non-blocking tasks. – Matthew Ferguson Feb 25 '16 at 09:32
1

A simple recursive solution to ensure serial operation.

func serialisedRequests(session: URLSession, requests: [URLRequest], index: Int = 0) {

    if index >= requests.count {
        return
    }

    let task = session.dataTask(with: requests[index]) {
        data, response, error in
        serialisedRequests(session: session, requests: requests, index: index+1)
    }
    task.resume()
}
naim
  • 11
  • 2
-2

Simply set your NSURLSession's HTTPMaximumConnectionsPerHost property to 1 and add the tasks in the order you want.

See this answer for more details: https://stackoverflow.com/a/21018964

Community
  • 1
  • 1
Eric
  • 16,003
  • 15
  • 87
  • 139
  • 1
    The only trouble with this approach is that it wouldn't work if the downloads were happening from different hosts. – Steve Wilford Jul 13 '15 at 14:08
  • See [_this_ more recent answer](http://stackoverflow.com/a/32655590/1375695) - which refers us back to this question's accepted answer - for why `HTTPMaximumConnectionsPerHost` is not going to help. – foundry Oct 29 '15 at 12:34