How should you handle errors in a production environment? - ios

I've been following along with some tutorials and have built an app that I'd like to publish, but I'm unsure how to deal with error handling.
Tutorials teach you about things like do-catch blocks but whenever they deal with the case where an error is thrown, they just log the error to console, which is obviously unhelpful in a production environment.
Moreover, some examples of the do-catch blocks appear to have little use. As an example, I'm using Realm Database as a core part of my app, and I'm told the way to initialise it is like so in the AppDelegate:
do {
_ = try Realm()
} catch {
// TODO: - Handle Exception
}
But in this case, since realm is a core part of my app, I wouldn't even mind if it crashed the app, because if it doesn't init properly it's going to crash later anyway.
There are obvious cases when I should just display an error to the user (e.g. If you're trying to connect to an API but there is no wifi), or where a default value could be provided, but I don't know what to do here.

There's no single good approach to handling errors, since how an error should be handled completely depends on the exact situation.
In general, however, there are some guidelines on error handling:
Use do-catch blocks on throwable functions unless you have a very
good reason to do so
Avoid runtime errors at all costs and don't throw fatal errors. Even if your app encounters an error that cannot be properly handled (the results of a throwable function is absolutely needed for your app to function), don't let your app crash due to a runtime error, rather let the user know that there's something wrong. A crashing application sends a very bad message to your users, while failing gracefully after encountering an error doesn't seem as bad.
In a catch block, try to solve the error without
letting the user know that there was an error if the error is
recoverable (for instance, you can use a default value instead of the
value you expected from the throwable function)
If the error can't be
handled, let the user know that there was an error (such as they
don't have internet connection when making a network request or a
network request failed for any other reason) and try to give them an
alternative approach (i.e. try later when you have internet
connection)
Only use try! and similar forced methods (force unwrapping, etc.) if you are absolutely sure that the function won't actually throw an error (for instance Realm.init() can only throw an error on the very first call to it in an app's lifecycle, so after the first instantiation of Realm, you can safely do try! Realm()) or if the error represent a programmer error (such as a file not being in the right place, which is needed for the application), but make sure you actually correct such errors in the development phase

Related

Is it sensible to mark Firestore throwing functions with try! in Swift?

Functions like .setData(from:completion:) and .addData(from:completion:) can throw an error synchronously. I haven't been able to find too much information online on what these errors could be so I'm assuming it'll only throw when encoding fails. Since it should be impossible in my code for encoding to fail I started marking these function with try! to disable error handling. Is this a sensible thing to do considering encoding should practically never fail in my code?
This is what I've done:
try! docRef.setData(from: book) { error in
// ^ Synchronous errors aren't handled because it should be
// impossible for encoding to fail. If it fails then I've
// done something seriously wrong in my code. Therefore
// I mark it with try!
completion(error) // Asynchronous errors are handled
}
A precursor to this question is; is it sensible to use in production code and/or during development? I think it makes a difference. As Rengers said it would be annoying to users if it turns out there is some sort of a bug. On the other hand if used during testing and development I think it's an appropriate way to detect extremely rare edge case.
Nonetheless, I think it's important to mention the power of habit. You might end up on a bit of a slippery slope once you start using this.
I think the question here is: "How do you want to go forward when the functies does throw?".
If there is a sensible way to recover from this, I would just catch the error and handle it gracefully. Show an error, do nothing, etc.
If there is no sensible way to recover at all, then your only option might be to crash the app. In that case try! makes sense, but your users will be annoyed when it crashes.
Personally I think there is never a good excuse to let an app crash in production, unless perhaps your entire app state is borked and there are no way to handle this, but that's something that has never come up in my code.

What to do about potential error 'throws' before submitting app to app store?

Lets say we have an app that we are about to submit to the app store... but it contains the following code:
try? audioSession.setCategory(AVAudioSessionCategoryAmbient)
if error != nil {
// this should never happen
}
try? audioSession.setActive(true)
if error != nil {
// this should never happen
}
We have to "try" the setCategory method because it "throws" ... but I have no idea why it would ever throw/fail.
I feel like this will be viewed as trash code... and that I should be doing more than just saying "this should never happen," but if an error did happen, I would have no idea what to do with it.
This documentation doesn't give any reasons why there would be an error, either.
Am I supposed to make a pop-up window that says "sorry... we encountered an error while attempting to set the category on the audioSession singleton instance... but the reason for this error is beyond this app's control. Press OK so the app can close. This never happened during app testing... so I have no idea why it happened to you now... must have something to do with your device. Maybe reboot? Better luck next time."
Or am I supposed to "elegantly handle the error?" How can I do that when I don't know what the potential error "throws" are and more importantly the potential reasons behind them.
Thanks for any suggestions. :)
I see two strategies that you could adopt.
Assume that the only way to throw an error is to pass in an invalid category string. Since your string is hard coded to one of the supplied constants, you can assume this will never happen. In this case, make the try cause a fatal error if it throws. e.g.
try! audioSession.setCategory(AVAudioSessionCategoryAmbient)
or
do
{
try audioSession.setCategory(AVAudioSessionCategoryAmbient)
}
catch
{
fatalError("setCategory failed with error: \(error)")
}
Assume there may be some local reason why it hasn't worked. In this case, use a do ... catch to catch the error and report it to the user. This doesn't have to happen in the function that calls audioSession.setCategory, you can choose to allow it to throw and allow other functions in the call stack to throw until you get to a point where you can easily report the error. If you get to this point, make sure the intermediate throwing functions are capable of clearing up their state by using defer and catch blocks.
If there is even a remote chance this might happen, and even though you have no proper way to handle it (let us say that the app is built upon that feature and if it is not working then the app becomes useless) - even then I would recommend putting there some nice handling code - I believe it's better than just let the app crash.
I would probably ask the user to report this to you with some details on their setup (after all, the error itself should contain at least a message that might give you a hint what went wrong).
If the app crashes, I would just assume the developer made some mistake. If a popup tries to at least broadly explain that there were some problems with obtaining an audio session, I would at least know that the developer tried to do something about it.
Basically, while you cannot do anything about recovering from the error, you can do at least something about user experience.
If you are sure errors will never occur you can force try
try! audioSession.setCategory(AVAudioSessionCategoryAmbient)
try! audioSession.setActive(true)

How to handle errors in NSPersistentContainer.loadPersistentStores?

My AppDelegate came with the following all familiar Core Data template:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "newsapp")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
It also had the following comment:
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
Replace this implementation with code to handle the error appropriately.
fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
The parent directory does not exist, cannot be created, or disallows writing.
The persistent store is not accessible, due to permissions or data protection when the device is locked.
The device is out of space.
The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
True, calling it quits in this place is a bad idea not only it is not allowed in production apps, but because, as I read elsewhere, if a data store gets corrupted for any reason, a user would have to reinstall the app not knowing about that. A user can delete and forget my app just as well in this case.
Now, how one is supposed to handle such and other errors here? Even if I write the error handling code, how do I test that is works correctly if these errors almost never happen?
I looked all over, but couldn't find any example.
In most cases fatalError is the only "handling" that makes sense for this error, though you might want to display an alert telling the user what's happening first. If you need your persistent store but you can't load it, you're kind of screwed.
Some of those errors are things that should come up during development. Like store migration or data protection problems-- you need to be testing that, and if necessary fixing the app before release. For cases like that, fatalError actually makes sense because only you will ever experience it.
Of those listed examples, the only one that might come up unexpectedly that can be awkward to test is the device being out of space. You could check available space and alert the user. But you still don't have any way to recover from the error unless your app is using a lot of space that you can clear out. If this happens, iOS will already be warning them that they're running out of space, so doing it yourself isn't necessary.
If you don't have a lot of data you can clear out, fatalError still makes sense here.

Error vs. Exception in Dart

Why are there Errors AND Exceptions in Dart and not either Errors OR Exceptions?
Is the reason a historical one oder what?
I can throw an Error, I can throw an Exception. None of them is checked by the Analyzer like in Java (Exception vs. RuntimeException)
From this post, quoting Bob Nystrom:
Error and its subclasses are for programmatic errors. If one of those
occurs, your code is bad and you should fix your code.
Non-Error exception classes are for runtime errors. Sometimes you can
prevent them from being thrown, but often you cannot.
Except in a few special circumstances, idiomatic Dart should throw
Errors, but never catch them. They exists specifically to not be
caught so that they take down the app and alert the programmer to the
location of the bug.
In other words, you should expect (and check for) exceptions (it is intended that you should handle them). If you get an error, then you need to check how you're using the API that's throwing the error - you're probably using it wrong.
If you're writing an API, then you should use the same pattern. Errors are messages to downstream developers about how they are using your API.
Exception
An Exception in Dart should be thrown for regular, expected program flow and is intended to be caught:
An Exception is intended to convey information to the user about a failure, so that the error can be addressed programmatically. It is intended to be caught, and it should contain useful data fields.
Example: TimeoutException
A TimeoutException will be thrown "when a scheduled timeout happens while waiting for an async result", which is expected program flow.
If we have a download task for example and that download task is not finished after our specified timeout time of thirty seconds (which can happen), we want to communicate that to our user, hence, we need to catch the Exception.
Error
An Error in Dart should be thrown for unexpected program flow and should not be caught but addressed by the programmer:
An Error object represents a program failure that the programmer should have avoided.
Example: AssertionError
An AssertionError is thrown "when an assert statement fails", i.e. it should never happen because we assert that it should not.
If we see such an error, it means that we should change our code and we should definitely not catch the error.
In practice you can catch Errors, but you should not. There is a linter rule to help enforce that.
The fact that Dart allows it can still be useful, e.g. when testing assertions or other errors.
See this answer for a complete example scenario.
Exceptions are considered conditions that you can plan ahead for and catch.
Errors are conditions that you don't expect or plan for.
For more detailed ans
Thanks to Chris and from here
Exceptions should be used when there is a problem that is expected. A common one is any type of I/O operation (like network traffic), where the socket closes early, and trying to write data to that socket fails.
Errors occur when there is a problem that was not expected. Things like null pointers (you expected this variable to not be null), running our of memory, etc... When you try to use the API in a wrong way or stuffs like that.
For the most part you, as an app developer, will always use exceptions. Errors tend to be reserved for unexpected and fatal problems.

iOS: Prevent library from crashing entire app

I've written a library for iOS that is referenced via a single .h file (MyAdaptedViewController.h).
This is essentially a view that sits inside a full UIViewController.
However due to the nature of the library, it may crash (dealing with signal processing, network connectivity and audio input/output).
What I would like would be for the entire app be protected from crashing if a crash occurs in the single UIViewController, i.e. if a crash occured, the user could continue using the app while MyAdaptedViewController was disabled. I understand that this will depend on the type of crash, but I was hoping most crashes / exceptions could be caught?
E.g.
would #try{}#catch{} or
void uncaughtExceptionHandler(NSException *exception)
be possible solutions?
NO! Catching exceptions in an iOS app is a exception to normal Cocoa coding conventions.
NSException is for exceptional errors that you can not be expected to recover from at run-time.
NSError is for errors you are excepted to recover from, or at least show the user, at run-time.
Do not try to handle every error that can be raised by catching exceptions. The APIs that Apple provide will only raise exceptions if you have done a programming error. For example if you try to access an object index out of bounds in an array, this is a programming error since you should have gotten your index correct before accessing the object.
For all other cases you should use instances of NSError, just as Apple do.
If it is proper to do so handle the error in your lib internally. If not possible pass the error up to your caller, and let them handle it.
I have written a longer blog post on the topic here: http://blog.jayway.com/2010/10/13/exceptions-and-errors-on-ios/

Resources