0

Based on https://stackoverflow.com/a/27712427/4166655, I found that I can create a UIImage with this code

func downloadImage(from url: URL) {
    print("Download Started")
    getData(from: url) { data, response, error in
        guard let data = data, error == nil else { return }
        print(response?.suggestedFilename ?? url.lastPathComponent)
        print("Download Finished")
        DispatchQueue.main.async() {
            self.imageView.image = UIImage(data: data)
        }
    }
}

I saw many UIImageView extensions incorporating this code, but I wondered if I could make this work as a direct init for UIImage.

I came up with this —

extension UIImage {
    convenience init?(url: String) {
        guard let urlObj = URL(string: url) else { return nil }
        self.init(url: urlObj)
    }

    convenience init?(url: URL) {
        let semaphore = DispatchSemaphore(value: 0)
        var imageData: Data? = nil

        URLSession.shared.dataTask(with: url) { (data, response, err) in
            // breakpoint put here
            guard let data = data else { return }
            imageData = data
            semaphore.signal()
        }
        semaphore.wait()

        if let data = imageData {
            self.init(data: data)
        } else {
            return nil
        }
    }
}

But this just times out, and my breakpoint (noted in the code) never triggers.

What's wrong with this approach?

Ronak Shah
  • 936
  • 9
  • 17
  • 1
    Waiting on a completion handler like this defeats the entire point of the completion handler: you're blocking the main thread, and freezing the UI, to wait for a web request to finish (which it may never do). – Alexander Feb 13 '20 at 23:05
  • Fair point - at this point I'm just curious why this doesn't even work – Ronak Shah Feb 13 '20 at 23:07

1 Answers1

2

my breakpoint (noted in the code) never triggers

Because you never actually started the URLSession data task (by telling it to resume):

    URLSession.shared.dataTask(with: url) { (data, response, err) in
        // whatever
    }.resume() // <-- this is what's missing

However, your entire approach here is wrong:

  • You cannot "pause" during an initializer and "wait" for some asynchronous operation to complete while blocking the main queue. You need to return the initialized instance right now.

  • If this were not an initializer and were happening on a background queue, the best way to "wait" for another background operation to finish would be with a DispatchQueue.

matt
  • 515,959
  • 87
  • 875
  • 1,141