0

I don’t know how to execute multiple NSUrlSession tasks one by one. When first task finishes start second task, when second task finishes start third task etc

For example I want to download multiple files from a web service.

I want them to begin downloading one by one.

For example files: 1.png , 2.png , 3.png

I want them to be downloaded in the same order I wrote them.

How can I do that with NSUrlSession?

  • Start each new task in the completion handler of the previous task. – Gereon Jul 28 '19 at 19:06
  • @Gereon That is wrong.... I am not looking for this type of solution. – Jason The Dynamite Jul 28 '19 at 19:07
  • 1
    You can build some data structure and use it as a queue so your completion handler checks the queue and performs the next fetch if there is work to do, but in essence @Gereon is correct – Paulw11 Jul 28 '19 at 19:16
  • @Paulw11 I cannot do it this way. I execute these download tasks or http requests from different parts of the app. Please provide a different solution. Maybe something with a DispatchQueue? – Jason The Dynamite Jul 28 '19 at 19:19
  • 1
    You can't use a serial dispatch queue since `task.resume` is non-blocking and dispatches work asynchronously. The only point at which you know the data task is complete is in the completion handler of that data task. You need to build your own queue (an array would do) and have your completion handler dispatch the next item from the array. It shouldn't matter that you are doing it from different parts of your app as long as you use a single queue instance. Be sure to guard your array updates from multiple threads by using something like a serial dispatch queue – Paulw11 Jul 28 '19 at 19:23
  • I don't have time to write out the code, but it would be easy to do this with an `NSCondition` and a dispatch queue. Each download would dispatch a block that uses a `wait` loop and a predicate indicating that there are no other active requests (a simple counter will do). Once it's zero, increment said counter and start the request. The completion block of each request simply decrements the counter and `signal`s the condition. Use a global queue if you don't care about order, or create your own sequential queue if you do. – James Bucanek Jul 29 '19 at 21:44
  • Reckos, are you aware that if you download them sequentially, the process will be much slower than if you allow them to download concurrently? So, not only are you going to have to write more code to make it run sequentially, but it will be slower, too. So, this begs the question as to why you want these downloads to run sequentially. It can be done, but is generally limited to those cases where you are absolutely forced to do so (e.g. the output of query 1 is required as input for query 2). I’d suggest you outline the broader objective, and we can advise you the best way to tackle that. – Rob Aug 19 '19 at 15:52
  • I just wanted to make it work just like JobIntentService / IntentService on android because I can easily make a queue of categorized task. I can make a JobIntentService which downloads data only related to products and I can make another JobIntentService which will download data only for discounts or something else. Currently on iOS i cant make it to work sequentially as easy as it is on android. – Jason The Dynamite Aug 30 '19 at 07:05

1 Answers1

1

A couple of things:

  1. Traditional iOS solution is to wrap the network request task in an asynchronous Operation subclass (e.g., such as outlined in point 3 of this answer; see this answer for another permutation of that base AsynchronousOperation). Just set the queue’s maxConcurrentOperationCount to 1 and you have serial/sequential download of images.

  2. But you really don’t want to do that because you pay huge performance penalty by downloading images sequentially. The only time you should perform network requests sequentially is if you absolutely need the response of one request in the preparation of the next request (e.g. a “login request” retrieves a token and a subsequent request that uses the token for authentication purposes).

    But when dealing with a series of images, though, it’s going to be much faster to download the images concurrently. Store them in a dictionary (or whatever) as they come in:

    var imageDictionary: [URL: UIImage] = [:]
    

    Then:

    imageDictionary[url] = image
    

    Then, when you want the images in order of your original array of URLs, you just lookup the image associated with a URL, for example:

    let sortedImages = urls.compactMap { imageDictionary[$0] }
    
  3. Alternatively, you might completely rethink the notion of downloading a series of images up-front. For example, if these are image view’s in a table view, you might just use a third party library, such as Kingfisher, and use its UIImageView method setImage(with:):

    let url = URL(string: "https://example.com/image.png")
    imageView.kf.setImage(with: url)
    

    This gets you completely out of the business of fetching images and managing caches, low memory warnings, etc. This just-in-time pattern can result in more performant user interfaces. And even if you really want to download them in advance (a bit of an edge case, so make sure you really need to do that), libraries like Kingfisher can manage that too.

Rob
  • 415,655
  • 72
  • 787
  • 1,044