How to use OperationQueue to handle URLSessions for an image downloader

988 Views Asked by At

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!

2

There are 2 best solutions below

2
Nathan Day On

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.

0
Rob On

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.