0

I am using this code

let url = URL(string: "http://image.tmdb.org/t/p/w185" + movie.poster_path!) // https://www.themoviedb.org/talk/568e3711c3a36858fc002384
print(url!)
DispatchQueue.global().async {
    let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
    DispatchQueue.main.async {
        self?.movieImage.image = UIImage(data: data!)
    }
}

from this stack overflow post. I have a URL with an image on it, I would like to use that URL to bring the image into my app and have it show up in a

@IBOutlet weak var movieImage: UIImageView!

but for some reason, I am getting an error saying that data is nil. Why would data be nil if the URL is valid? Is this an issue with the contentsOf function or am I doing something wrong here?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
swiftthrow
  • 23
  • 6
  • 2
    **Never** load data from a remote URL with synchronous API `Data(contentsOf`, not even on a background thread. – vadian Jan 13 '20 at 19:08
  • Oh, I was under the impression that the background thread would make this asynchronous. Is the issue that if the loading of data takes a while the background thread will be blocked? – swiftthrow Jan 13 '20 at 19:09
  • Yes it blocks the thread which it's running on. – vadian Jan 13 '20 at 19:11
  • Forgive my ignorance, but why is this such a bad thing if it is the background thread? Is there only one main thread and one background thread? I suppose it's better practice to just make sure it can't block any thread in the first place even if there are several background threads. – swiftthrow Jan 13 '20 at 19:14
  • 1
    Please read the [documentation](https://developer.apple.com/documentation/foundation/nsdata/1413892-init): ***Important: Don't use this synchronous initializer to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.*** – vadian Jan 13 '20 at 19:18
  • Okay, based on this documentation, I'm going to try a dataTask instead, and if I understand correctly the completionHandler will take care of it once the data has been loaded. contentOf should have been used for URL's local to the device? Or a local network? Or what kind of local URL? – swiftthrow Jan 13 '20 at 19:20
  • This API should be used only for file system URLs and yes, the completion handler manages the correct timing. – vadian Jan 13 '20 at 19:22

2 Answers2

0

If you try changing your URL declaration to be: let url = URL(string: "http://image.tmdb.org/t/p/w185//nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg") it works as expected. So perhaps you are not assembling the URL correctly?

I would print whatever URL you're creating and try visiting the website to see if it is actually correct

Logan
  • 1,172
  • 9
  • 23
  • I don't think this is it because I actually tested the URL by assembling my URL, printing it, and then pasting the printed URL into my browser and seeing the expected image. – swiftthrow Jan 13 '20 at 19:14
0

I’d suggest not using try? (which discards any meaningful error data) and instead use try wrapped in a do-catch block, and in the catch block, examine what the error is. Right now, you’re flying blind.

Or, better, use URLSession.shared.dataTask(with:) and look at the error in the completion handler.

You asked:

... but why is this such a bad thing [to use Data(contentsOf:)] if it is the background thread?

Yes, by dispatching this to a global queue you’ve mitigated the “don’t block the main thread” problem. But Data(contentsOf:) doesn’t provide much diagnostic information about why it failed. Also, it ties up one of the very limited number of worker threads that GCD draws upon. If you exhaust the worker thread pool, then GCD won’t be able to do anything else until it’s freed up. Using URLSession offers the chance to do more meaningful diagnostics and avoids blocking GCD worker threads.

So, I would suggest removing all of those ! forced unwrapped operators and not using Data(contentsOf:). Thus, I might suggest something like:

guard
    let path = movie.poster_path,
    let baseURL = URL(string: "http://image.tmdb.org/t/p/w185")
else {
    print("problem getting path/URL")
    return
}

let url = baseURL.appendingPathComponent(path)

URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
    guard
        let data = data,
        let response = response as? HTTPURLResponse,
        error == nil
    else {
        print("network error:", error ?? "Unknown error")
        return
    }

    guard 200..<300 ~= response.statusCode else {
        print("invalid status code, expected 2xx, received", response.statusCode)
    }

    guard let image = UIImage(data: data) else {
        print("Not valid image")
        return
    }

    DispatchQueue.main.async {
        self?.movieImage.image = image
    }
}.resume()

Then, by displaying the error, if any, we’ll see what the problem was. FWIW, the above network request identifies three types of errors, which might be helpful for diagnostic purposes:

  • Basic network errors
  • HTTP errors
  • Content errors (not an image)
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • It's complaining about App Transport Security. Edit: I had to make a change to my info.plist to whitelist the API I'm using since the image is via http. – swiftthrow Jan 13 '20 at 21:08
  • Yep, that makes sense. Hope this helped diagnose the issue! – Rob Jan 13 '20 at 21:35