When to use do-catch block using Swift - ios

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.

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.

Data contentsOf yields nil, but the URL is valid

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)

How can I fix "Data(contentsOf: url) crashing app"?

When I run data = try! Data(contentsOf: url) my app crashes, and I'm getting this error. I tried to add the UIConstraintBasedLayoutDebugging breakpoint but it didn't help a lot.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
2019-09-23 15:59:55.228440-0600 Biobot[15968:547259] -[LGSideMenuController isLoading]: unrecognized selector sent to instance 0x7f9cf90a1a00
Warning: hit breakpoint while running function, skipping commands and conditions to prevent recursion.
error: Execution was interrupted, reason: breakpoint 4.1.
The process has been returned to the state before expression evaluation.
I don't even have an [LGSideMenuController isLoading] in my code. I'm really lost this time because the code worked just fine this morning. I updated my xcode to version 11, I don't know if that could be causing the problem
Update:
You should use Data(contentsOf: url) only when loading data from local storage (so only url that starts with file://). When you are loading data from the Internet you should use URLSession:
URLSession.shared.dataTask(with: url) { (data, response, error) in
DispatchQueue.main.async { //all changes to UI must be called on main thread
if let error = error {
print("Error: \(error)")
return
}
//transform your data and update UI
self.label.text = "Loaded data: \(data)"
}
}.resume()
where self.label.text = "Loaded data: \(data)" is some example of what you can do with your fetched data.
Try to avoid try!. Instead of it use do{} catch {}. Your code probably won't work as expected (it will not fix the problem, but you will be able to get more details). So in your code do:
do {
data = try Data(contentsOf: url)
} catch {
print("\(error)")
}
For me, your error doesn't look like related to data loading. It looks more like an issue with memory handling, as you have a pointer that is pointing to an instance of LGSideMenuController and you (or one of the libraries that you are using) try to call isLoading method on it.

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

Swift 2.0 Call Can Throw but is not marked with try

I'm trying to update the code in my app after the update to XCode 7 and it looks like I'm going to have to go through a serious learning curve again just to catch up. What am I doing wrong in the code below?
Is if let still being used?
I am so not familiar with try/catch outside of C#. I don't know how to use it in the context of swift and it'd be great to find an easy to understand guide that doesn't assume that I ever knew Objective C or have ever come across this before.
Use this instead:
do {
let json = try NSJSONSerialization.JSONObjectWithData(...)
return json
} catch let error as NSError {
print("JSON Error: \(error.localizedDescription)")
}
You are calling a method that throws a Swift error and as such, it needs to be marked with try.
do
{
let json = try NSJSONSerializer.JSONObjectWithData(...)
return json
}
catch
{
// By default the catch clause defines the variable error as whatever ws thrown
print("Error is \(error)")
return nil
}
Is pretty much what you want.
In case of my understanding is
let result: AnyObject? = try! NSJSONSerialization.JSONObjectWithData(jsonData!, options: NSJSONReadingOptions.MutableContainers)

Resources