0

I'm trying to create a simple ImageDownloader framework in swift.

What I'd like to achieve:

  1. Able to download images with given URLs
  2. Cache with url string

So fetching just one image is no problem, I just used func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask from URLSession to get the data and pass it into UIImage.

However, my question is, how should I change it into a framework that supports concurrent download many images at the same time?

Should I used OperationQueue and every time the task is created with an url, add that task into the queue? Is this necessary?? e.g.:

let oq = OperationQueue()

let urlArray = ["url1", "url2" ....]

for url in urlArray {
    oq.addOperation {
        self?.fetchImage(with: url, placeHolder: nil) { [weak self] result in
            switch result {
            //...
        }
    }
}
    

Thanks!

RobotX
  • 208
  • 2
  • 15
  • 2
    URLSession already supports multiple simultaneous downloads, so perhaps you are overthinking this? You might be interested in seeing my approach: https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch24p842downloader/ch37p1099downloader/Downloader.swift – matt Feb 23 '22 at 03:10
  • You could look at using Swift `async/await` framework instead (I strongly recommend this). It is designed to be the framework of choice for concurrent/parallel tasks. In particular using such construct as `await withTaskGroup(of: ...)` to supports concurrent download of many images at the same time. – workingdog support Ukraine Feb 23 '22 at 05:17
  • if the main purpose of your post is to "....trying to understand how operationQ works together with urlSession tasks.", then make sure your question reflect that. – workingdog support Ukraine Feb 23 '22 at 23:07

2 Answers2

0

No, it uses a closure to return a result because it already does this for you, you are not going to block on the call, you will trigger downloading, probable using a thread, and then when its finished it will call the result closure, because of this you need to be aware that your app state could have changed when you get your result.

Nathan Day
  • 5,981
  • 2
  • 24
  • 40
  • ok thanks, so no concurrentQ (gcd) needed. But what about operationQueue? Why do many imageDownloader 3rd libs (e.g sdwebimage) like to put the download network call inside a operationQueue? – RobotX Feb 23 '22 at 19:14
  • It could be they have additional work they want to do on the result, maybe only one thing will download at a time without it, not sure, we did in one project where two things would have the download at the same time and then when they both had finished the results where used to download a third thing, I don’t remember the exact reason, I didn’t right it may have been to make sure the two initial downloads complete before the third could start. – Nathan Day Feb 23 '22 at 22:15
-1

As you have likely figured out by now, your rationale for wanting to use operations is based upon some faulty assumptions. You simply do not need operations if all you want is to support concurrent network requests. That is an inherent feature of URLSession, namely that all network requests are asynchronous. You actually have to contort yourself to not have network requests run concurrently (and most attempts to do so that I've seen on this forum are ill-advised; noobs seem to love semaphores; lol).

That having been said, there might be reasons why you would want to use operation queue, e.g.:

  1. If you wanted concurrency, but you wanted to control the degree of concurrency (e.g., not more than four concurrent requests at a time).

  2. If you wanted dependencies between operations.

  3. You want to cancel all the operations on a queue.

But the proper use of operation queues for an asynchronous task, such as a network request, requires some care. Your attempt outlined in your question is creating an operation for the initiation of a network request, but it does not wait for the response. The operation is finishing immediately. That defeats the purpose of operation queues.

You would want to create an Operation subclass that manually managed the KVO associated with the various isExecuting, isFinished, etc., properties. See the Operation documentation. Or see Trying to Understand Asynchronous Operation Subclass for a general discussion about how to create asynchronous Operation subclasses. Or see WWDC 2015 Advanced NSOperations (which uses Objective-C, but the concepts associated with asynchronous operations is discussed; but this video is outdated and they actually pulled their code samples because they had issues in their (IMHO) over-engineered solution). For a practical example of operation queues with network requests, see How To Download Multiple Files Sequentially using NSURLSession downloadTask in Swift for example with download tasks. The same pattern can be used for data tasks, too.


All of that having been said, using operations is a bit of an anachronism at this point. If supporting iOS 13 and later, I use async-await and Swift concurrency. Even Combine offers better patterns for managing dependencies of asynchronous tasks than operation queues. Do what you want, but, IMHO, it might not be a good investment of time to learn the intricacies of proper asynchronous Operation implementations nowadays, when there are newer patterns that are so much better.

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