Data contentsOf yields nil, but the URL is valid - ios

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?

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

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)

Related

IOS app crashes on a line of code if there's no internet connection, how can I prevent this

The code this and it crashes on "try!", but I don't know how to catch the error and it has it be explicit otherwise it won't work.
func downloadPicture2(finished: () -> Void) {
let imageUrlString = self.payments[indexPath.row].picture
let imageUrl = URL(string: imageUrlString!)!
let imageData = try! Data(contentsOf: imageUrl)
cell.profilePicture.image = UIImage(data: imageData)
cell.profilePicture.layer.cornerRadius = cell.profilePicture.frame.size.width / 2
cell.profilePicture.clipsToBounds = true
}
The short answer is don't use try! - Use do/try/catch and recover from the problem in the catch clause.
For example -
func downloadPicture2(finished: () -> Void) {
cell.profilePicture.image = nil
if let imageUrlString = self.payments[indexPath.row].picture,
let imageUrl = URL(string: imageUrlString) {
do {
let imageData = try Data(contentsOf: imageUrl)
cell.profilePicture.image = UIImage(data: imageData)
}
catch {
print("Error fetching image - \(error)")
}
}
cell.profilePicture.layer.cornerRadius = cell.profilePicture.frame.size.width / 2
cell.profilePicture.clipsToBounds = true
}
Now you have code that won't crash if the url is invalid or there is no network, but there are still some serious issues with this code.
Data(contentsOf:) blocks the current thread while it fetches the data. Since you are executing on the main thread this will freeze the user interface and give a poor user experience.
Apple specifically warns not to do this
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.
Rather, you should use an asynchronous network operations, such as a dataTask.
This code operates on cell - an external property. Once you move to asynchronous code you will probably be fetching images for multiple cells simultaneously. You should pass the relevant cell to this function to avoid clashes.
The use of the network isn't particularly efficient either; assuming this is part of a table or collection view, cells are reused as the view scrolls. You will repeatedly fetch the same image as this happens. Some sort of local caching would be more efficient.
If it is possible to use external frameworks in your project (i.e. your employer doesn't specifically disallow it) then I strongly suggest you look at a framework like SDWebImage or KingFisher. They will make this task much easier and much more efficient.

What is difference between URLSession vs GCD in terms of download image from image url?

What is difference between URLSession vs DispatchQueue.global().async + Data(contentsOf: ) in terms of download images from image urls?
func loadImageWithUrlSession() {
guard let url = URL(string: IMAGE_URL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let data = data else { return }
let image = UIImage(data: data)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.urlSessionImageView.image = image
}
}.resume()
}
func loadImageWithGCD() {
DispatchQueue.global(qos: .background).async {
guard
let url = URL(string: self.IMAGE_URL),
let data = try? Data(contentsOf: url) else {
return
}
let image = UIImage(data: data)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.gcdImageView.image = image
}
}
}
I know that URLSession can cancel or suspend task.
But if I use Rx instead, I can do the same thing as above as well.
I had an experiment that and it was depending on which QoS I'm using.
By the way, .userInitiated QoS was way faster than URLSession.
Which one are you guys use for something like downloading task with a background thread and why?
Is there any kind-teacher can help me specifically?
URLSession offers far greater configuration control, diagnostics of failures, cancelation, background sessions, ability to download directly to persistent storage to minimize peak memory usages, etc. URLSession and Data(contentsOf:) just are not comparable on feature set.
The synchronous Data(contentsOf:) unnecessarily blocks GCD worker threads and is also susceptible to misuse. It also is quite limiting and you will easily find yourself regretting the decision in the future (e.g. you later add some authentication process; you want to customize the cache behaviors, you want to parse and act upon status codes in the responses, you need cancelation capabilities because you are retrieving images for collection or table views, etc.).
It’s illuminating to look at the documentation for one of the init with a URL methods for Data, where it warns us:
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.
Instead, for non-file URLs, consider using the dataTask(with:completionHandler:) method of the URLSession class. See Fetching Website Data into Memory for an example.
Yes, dispatching this to a background thread addresses many of the above concerns, but Apple didn’t just suggest “just dispatch this to some background queue,” but rather explicitly advised to use URLSession instead. While your use of GCD global queue avoids some of issues that Apple warns us of above, it also imposes many unnecessarily limitations. If you use Data(contentsOf:), this is a decision that you’ll likely regret/refactor in the future. You might as well use URLSession now.
Regarding Data(contentsOf:) being appreciably faster when using .userInitiated, vs .default or URLSession approach, usually the network latency and transmission time dwarfs any queue priority related factors, so I find that claim hard to believe. In fact, I just tested download of 50 images via GCD (using both .default and .userInitiated) and the speed was not appreciably different than URLSession approach.

When to use do-catch block using Swift

In the following scenario when reading JSON data from a file, I have the following block of code:
// Fetch URL
let url = Bundle.main.url(forResource: "sampleJSON", withExtension: "json")!
// Load Data
let data = try! Data(contentsOf: url)
// Deserialize JSON
let json = try! JSONSerialization.jsonObject(with: data, options: [])
Is this block of code correct on its own, or should would it be better practice to include it inside a do-catch block? I'm asking because I have seen scenarios when pulling data from the web using URLSession, where developers do the JSONSerialization inside of a do-catch block. Is there a reason why for doing it when using URLSession, and not when simply pulling the JSON data from a file? What is best practice for something like this?
1 - Is this block of code correct on its own, or should would it be better practice to include it inside a do-catch block?
A: This code is correct. It will work if your sampleJSON.json file is in your bundle AND the data in your JSON file is correctly formated AND the JSONSerialization succeds parsing the data provided.
2 - I'm asking because I have seen scenarios when pulling data from the web using URLSession, where developers do the JSONSerialization inside of a do-catch block. Is there a reason why for doing it when using URLSession, and not when simply pulling the JSON data from a file? What is best practice for something like this?
A: The do-catch statement is seen more often when consuming data(JSON in this case) from the web because the API might break for any reason(wrong specification of the data that must be shown, error in the web application itself, etc) and if this happens we do not want our application to crash.
I say CRASH because you used the ! which do not propagate the error to the upper layer of your application, it tries to force the operation and if fails would crash the app.
At this point you probably realized that the reason you don't see do-catch statement when consuming data from your bundle is because the app developer himself provided the JSON so I'd assume you are sure about the content of the file, but I'd still use the do-catch statement since something could go wrong and don't want my app to crash due to a silly thing like this.
TL;DR
I recommend to ALWAYS use error propagation with throws/rethrows or even the ? so you can test for nil results.
I have written a small article here with some tips and how it works in Swift 2.1, not much have changed in Swift 3.1 so you can use to study the do-catch statement.
I would rewrite the code you provided like this:
WARNING: UNTESTED CODE
enum JSONFromFileError: Error {
case fileNotInBundle(String)
case deserializationError
case getDataError(URL)
}
func json(from file: String) throws -> Any {
// Fetch URL in Bundle
guard let url = Bundle.main.url(forResource: file, withExtension: "json") else {
throw JSONFromFileError.fileNotInBundle(file)
}
// Load Data from url
guard let data = try? Data(contentsOf: url) else {
throw JSONFromFileError.getDataError(url)
}
// Deserialize JSON
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else {
throw JSONFromFileError.deserializationError
}
return json
}
do {
let myJsonObject = try json(from: "sampleJSON")
print(myJsonObject)
} catch let error {
print(error)
}
You should in general use a try-catch block for each function that is throwable. With your current code, if either of your try blocks fail (either the data can't be downloaded from the url or it is not a valid json), the forced trys will fail and result in a runtime exception.
let url = Bundle.main.url(forResource: "sampleJSON", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data, options: [])
} catch {
//handle error
}
If you don't care about the error that would be thrown be the function, you can use try? which returns a nil when the function would throw an error. This way your code won't crash.
guard let data = try? Data(contentsOf: url) else {return}
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else {return}
Firstly, It is recommended that we wrap any functions that throws (with try) into a do-catch block. In my own opinion, I will also perform a do-catch block in case I modify the sampleJSON file without me knowing, that causes the format of the JSON to be disturbed.
Secondly,it is definitely a good practise to never use force unwrap at all in the code.
Lastly, we should always catch for serialization exceptions and throw to the caller, which eventually will inform the view to throw an error dialog.

Accessing URL and Array from within a Block of JSON Data

Let's say I have JSON data structured in the following way:
{ "fruits" : {
"apple": {
"name": "Gala"
"color": "red",
"picture": "//juliandance.org/wp-content/uploads/2016/01/RedApple.jpg",
"noOfFruit": [1, 2]
}
}
How would I access the picture and noOfFruit array using the iOS version of Firebase? I want to make a table view with a cell that lists the apple's name, the color, a picture of the apple, and then lists the number of fruit. I get how to obtain the "color" and "name" values but how would I access the array and turn it into a string and the image so that it shows the image in the table view? Any help is appreciated!
For the array, it's really simple. Wherever you have your function that listenes for the firebase changes, I'll imagine that you have the info under the apple key stored in a variable like let apple
Then, you could cast the value of noOfFruit to an array, like the following:
let apple = // what you had before
guard let noOfFruit = apple["noOfFruit"] as? [Int] else {
return
}
//Here you have the array of ints called noOfFruit
For the image, theres several options out there. The first (and bad one) is to synchrounsly fetch the data of the url and set it to an image view as the following:
let url = URL(string: picture)
let data = try? Data(contentsOf: url!) //this may break due to force unwrapping, make sure it exists
imageView.image = UIImage(data: data!)
The thing with this approach is that it's NOT OK. It will block the main thread while its making the request and dowloading the image, leaving the app unresponsive.
The better approach would be to go fetch it asynchronously.There are several libraries that really help, such as AlamofireImage, but it can be done with barebones Foundation really easy. To do that, you should use URLSession class as the following:
guard let url = URL(string: picture) else {
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
return
}
//Remember to do UI updates in the main thread
DispatchQueue.main.async {
self.myImageView.image = UIImage(data: data!)
}
}.resume()

Swift 3 - How to handle URLRequest / URLSession errors?

I want to know how you guys handle errors when using a URLRequest in your app. How do you go about notifying your users that an error has occurred? Do you even notify your users at all? Do you try and reload the URLRequest again? Do you tell your users to close the current screen and open it again with an alert box? I have no clue.
Once there's an error, your app stops. So what do you do when this happens and you have a network issue, bad Json data?
What do you do when you get a "Bad Network Connection (The server is down)" or the URLSession comes back with an error and the internet connection is fine?
Please look at the code below and help me figure out what needs to be done when an error occurs.
let url = URL(string:"http://example/jsonFile.php")
var request = URLRequest(url:url!)
request.httpMethod = "POST"
let postingString = "id=\(id)"
request.httpBody = postingString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest){(data, response, error) -> Void in
if error != nil {
print("error \(error)")
// *****
// What do you do here? Do you tell your users anything?
// *****
return
}
// Check for Error
if let urlContent = data {
do{
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: .allowFragments) as! [String: AnyObject]
print("jsonResult \(jsonResult)")
}
catch{
print("JSON serialization failed")
// *****
// What do you do here? Do you tell your users anything?
// *****
}
}
}
task.resume()
It is often a bad idea to hide the errors and do nothing (see Error Hiding Anti-Pattern). Unfortunately, handling errors is not easy and can be tedious some times. Pretty code examples quickly become "uglier" when exceptions and errors have to be handled. It takes time to learn to do it well.
In the case of network connections, you would want to wrap your requests into asynchronous methods that take a completion callback as parameter. This callback will often receive a successful result or an error, sometimes wrapped in an enum to remove ambiguity (see Result as an example).
Once you know a network request has failed, you can present that information to the user in a clean, informational but non-obstructive way. To find out what other applications do in this scenario, turn on Airplane Mode in your phone and poke around.
Here are some examples:
Apple Music
Facebook Messenger

Resources