Swift Error handling best practices - swift

Im used to typical try catch blocks to handle exceptions of any function or block of code like objective C.
However, after making leap of faith to Swift and reading about Swift’s error handling; it’s not much what I expected. You must handle your exceptions by throwing errors and guard and checking if else everything.
I need general Swift error handling tips and best practices, and especially for the following cases:
Im using Alamofire for calling services, which has closure, and calling it through another function with closure too. AFAIK I can’t throw error inside async code, so what is best practice for such case? will try to update with code sample
Is it favourable to have every function in the app throw errors? Just in case ? Like checking every single value or result.
Can I have a Singleton Error handling module?
Thanks in advance

You must handle your exceptions by throwing errors and guard and checking if else everything.
This is an assumption that doesn't have to be true. If your code is structured correctly, you shouldn't have to check everything with if lets and throws.
I need general Swift error handling tips and best practices ...
Before you look at anything else, read the following pages in order. They should give you a good background on best practices.
Error Protocol Apple Developer Documentation
Error Handling - Apple's Swift Programming Language Guide
Magical Error Handling in Swift - Ray Wenderlich
Im using Alamofire for calling services, which has closure, and calling it through another function with closure too. AFAIK I can’t throw error inside async code, so what is best practice for such case?
You can. A good practice is to pass a closure as a parameter to your service-calling function, then call the closure when the asynchronous operation is complete, like so:
functionThatCallsAService(completion: #escaping (Data?, NetworkingErrors?) -> ()) {
session.dataTask(with: request) { data, response, error in
guard error == nil else {
completion(nil, NetworkingErrors.returnedError(error!))
return
}
completion(data, nil)
}.resume()
}
enum NetworkingErrors: Error {
case errorParsingJSON
case noInternetConnection
case dataReturnedNil
case returnedError(Error)
case invalidStatusCode(Int)
case customError(String)
}
Is it favourable to have every function in the app throw errors? Just in case? Like checking every single value or result.
If you know for sure that a function or value won't be nil/cause a runtime error/throw an error, then don't check for it! But generally, according to the web pages above, you should check for nil and/or errors from calling a web service, interacting with the file system, creating a complex object, etc.
Can I have a Singleton Error handling module?
You technically can, but I don't see any reason to do so besides logging.

enum CheckValidAge : Error{
case overrage
case underage
}
func checkValidAgeForGovernmentJob(age:Int)throws -> Bool{
if age < 18{
throw CheckValidAge.underage
}else if age > 25{
throw CheckValidAge.overrage
}else{
return true
}
}
do {
try checkValidAgeForGovernmentJob(age: 17)
print("You are valid for government job ")
}catch CheckValidAge.underage{
print("You are underage for government job ")
}catch CheckValidAge.overrage{
print("You are overrage for government job ")
}
try checkValidAgeForGovernmentJob(age: 17) OutPut : You are underage for government job
try checkValidAgeForGovernmentJob(age: 26) OutPut : You are overrage for government job
try checkValidAgeForGovernmentJob(age: 18) OutPut : You are valid for government job

Related

How to assert an error is thrown async when testing?

We can test thrown errors with XCTAssertThrowsError. Async things can be tested with expectation. I have some method which dispatch work to a background thread and can at some point throw an error.
Is it possible to expect an error be thrown somewhere in the future? I need to combine expectation and XCTAssertThrowsError I think, but I do not know how.
Reproduction project: https://github.com/Jasperav/ThrowingAsyncError. Just clone the project and run the tests, one of them will fail. I made a class which will crash after a few seconds after it has been allocated. I want to make sure it keeps crashing after a few seconds, so I want a test case for it.
You could fulfill an expectation in the catch block of a call that is expected to fail.
func testFailingAsyncCode() async throws {
let expectation = expectation(description: "expect call to throw error")
let dataFetcher = DataFetcher()
do {
// This call is expected to fail
let data = try await dataFetcher.fetchData(withRequest: request, validStatusCodes: [200])
} catch {
// Expectation is fulfilled when call fails
expectation.fulfill()
}
wait(for: [expectation], timeout: 3)
}
I took the sample code from David B.'s answer and changed it, because expectations are not needed when the unit test method is annotated with async.
func testFailingAsyncCode() async { // async is important here
let dataFetcher = DataFetcher()
var didFailWithError: Error?
do {
// This call is expected to fail
_ = try await dataFetcher.fetchData(withRequest: request, validStatusCodes: [200])
} catch {
didFailWithError = error
// Here you could do more assertions with the non-nil error object
}
XCTAssertNotNil(didFailWithError)
}
I took a look at the reproduction project to see what you were trying to accomplish here...
To my understanding:
XCTAssertThrowsError are assertions that takes in a block that can throw. They just happen to assert that an error is thrown in a synchronous block when it's done running.
XCTestExpectation are classes that keep track of whether or not requested conditions are met. They are for keeping track of asynchronous code behavior objects/references need to be kept and checked later.
What you seem to be trying to do is make something like XCTestExpectation work the same way XCTAssertThrowsError does, as in make an synchronous assertion that an asynchronous block will throw. It won't work quite that way because of how the code runs and returns.
The asynchronous code you refer to does not throw (timer initializer). As far as I know, there aren't any asynchronous blocks that can throw. Perhaps the question you should be asking is how can we make a synchronous operation choose to run synchronously sometimes, but also asynchronously when it feels like...
Alternatively for some additional complexity in every class you would like to test I've made a solution with what is almost bare minimum to make this easily testable and portable...
https://github.com/Jasperav/ThrowingAsyncError/pull/1/files
May I ask why you would ever want to do something like this?

Is it possible to throw a "RuntimeException" in Swift without declaring it?

I would like to throw an exception from some "deep" function, so it bubbles up to another function, where I want to catch it.
f1 calls f2 calls f3 calls ... fN which may throw an error
I would like to catch the error from f1.
I've read that in Swift I have to declare all methods with throws, and also call them using try.
But that's quite annoying:
enum MyErrorType : ErrorType {
case SomeError
}
func f1() {
do {
try f2()
} catch {
print("recovered")
}
}
func f2() throws {
try f3()
}
func f3() throws {
try f4()
}
...
func fN() throws {
if (someCondition) {
throw MyErrorType.SomeError
}
}
Isn't there a similar concept to the RuntimeException in Java, where throws doesn't leak all the way up the call chain?
Yes, it is possible!
Use: fatalError("your message here") to throw runtime exception
To elaborate on Максим Мартынов's answer, Swift has 3 ways to do throw undeclared, uncatchable errors (but other approaches are possible if you want to venture outside Swift's standard library). These are based on the 3 levels of optimization:
-Onone: No optimization; debug build
-O: Normal optimization; release build
-O SWIFT_DISABLE_SAFETY_CHECKS: Unchecked optimization; extremely optimized build
1. assertionFailure(_:)
Write this line when you're doing debugging tests and hit a line you don't think should ever be hit. These are removed in non-debug builds, so you must assume they will never be hit in the production app.
This has a sister function called assert(_:_:), which lets you assert at runtime whether a condition is true. assertionFailure(_:) is what you write when you know the situation is always bad, but don't think that'll harm the production code very much.
Usage:
if color.red > 0 {
assertionFailure("The UI should have guaranteed the red level stays at 0")
color = NSColor(red: 0, green: color.green, blue: color.blue)
}
2. preconditionFailure(_:)
Write this line when you're sure some condition you've described (in documentation, etc.) was not met. This works like assertionFailure(_:), but in release builds as well as debug ones.
Like assertionFailure(_:), this one's got a sister function called precondition(_:_:), which lets you decide at runtime whether a precondition was met. preconditionFailure(_:) is essentially that, but assuming the precondition is never met once the program gets to that line.
Usage:
guard index >= 0 else {
preconditionFailure("You passed a negative number as an array index")
return nil
}
Note that, in extremely optimized builds, it is not defined what happens if this line is hit! So if you don't want your app to wig out if it might ever hit this, then make sure the error state is handleable.
3. fatalError(_:)
Used as a last resort. When every other attempt to save the day has failed, here is your nuke. After printing the message you pass to it (along with the file and line number), the program stops dead in its tracks.
Once the program gets to this line, this line always runs, and the program never continues. This is true even in extremely optimized builds.
Usage:
#if arch(arm) || arch(arm64)
fatalError("This app cannot run on this processor")
#endif
Further reading: Swift Assertions by Andy Bargh
The error handling mechanism in Swift does not involve raising unchecked (runtime) exceptions. Instead, explicit error handling is required. Swift is certainly not the only recently designed language to go for this design – for instance Rust and Go also in their own ways also require explicitly describing the error paths in your code. In Objective-C the unchecked exception feature exists, but is largely used only for communicating programmer errors, with the notable exception of a few key Cocoa classes such as NSFileHandle which tends to catch people out.
Technically you do have the ability to raise Objective-C exceptions in Swift with the use of NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise() as is explained in this excellent answer to this question, arguably a duplicate of your question. You really shouldn't raise NSExceptions though (not least because you have no Objective-C exception catching language feature available to you in Swift).
Why did they go with this design? Apple's "Error Handling in Swift 2.0" document explains the rationale clearly. Quoting from there:
This approach […] is very similar to the error handling model manually
implemented in Objective-C with the NSError convention. Notably, the
approach preserves these advantages of this convention:
Whether a method produces an error (or not) is an explicit part of its API contract.
Methods default to not producing errors unless they are explicitly marked.
The control flow within a function is still mostly explicit: a maintainer can tell exactly which statements can produce an error, and
a simple inspection reveals how the function reacts to the error.
Throwing an error provides similar performance to allocating an error and returning it – it isn’t an expensive, table-based stack
unwinding process. Cocoa APIs using standard NSError patterns can be
imported into this world automatically. Other common patterns (e.g.
CFError, errno) can be added to the model in future versions of Swift.
[…]
As to basic syntax, we decided to stick with the familiar language of
exception handling. […] by and large, error propagation in this
proposal works like it does in exception handling, and people are
inevitably going to make the connection.
Isn't there a similar concept to the RuntimeException in Java, where throws doesn't leak all the way up the call chain?
Swift does indeed have error handling which doesn't propegate at compile time.
However, before I discuss those, I must say that the one you point out, where you use the language's do...catch, try, throw, and throws keywords/features to handle errors, is by far the safest and most preferred. This ensures that every single time an error might be thrown or caught, it's handled correctly. This completely eliminates surprise errors, making all code more safe and predictable. Because of that inherent compile- and run-time safety, you should use this wherever you can.
func loadPreferences() throws -> Data {
return try Data(contentsOf: preferencesResourceUrl, options: [.mappedIfSafe, .uncached])
}
func start() {
do {
self.preferences = try loadPreferences()
}
catch {
print("Failed to load preferences", error)
assertionFailure()
}
}
guard let fileSizeInBytes = try? FileManager.default.attributesOfItem(atPath: path)[.size] as? Int64 else {
assertionFailure("Couldn't get file size")
return false
}
Probably the easiest way to silence Swift's compiler is with try! - this will allow you to use native Swift errors, but also ignore them.
Here's what your example code would look like with that:
enum MyErrorType : ErrorType {
case SomeError
}
func f1() {
f2()
}
func f2() {
f3()
}
func f3() {
try! f4()
}
...
func fN() throws {
if (someCondition) {
throw MyErrorType.SomeError
}
}
Obviously, this has the problem of not allowing you to ever catch these, so if you want a silent error you can catch, read on.
There are also assertions, preconditions, and fatalErrors, which I described in detail in my answer from October of 2017. The compiler provides reasonable handling of these, such as ensuring that return statements and other control flow are placed and omitted when appropriate. Like try!, however, these cannot be caught.
exit is in this family if your goal is to stop the program immediately.
If you venture outside Swift into the wider Apple ecosystem (that is, if you are writing Swift on an Apple platform), you also see Objective-C's NSException. As you desire, this can be thrown by Swift without using any language features guarding against that. Make sure you document that! However, this cannot be caught by Swift alone! You can write a thin Objective-C wrapper that lets you interact with it in the Swift world.
func silentButDeadly() {
// ... some operations ...
guard !shouldThrow else {
NSException.raise(NSExceptionName("Deadly and silent", format: "Could not handle %#", arguments: withVaList([problematicValue], {$0}))
return
}
// ... some operations ...
}
func devilMayCare() {
// ... some operations ...
silentButDeadly()
// ... some operations ...
}
func moreCautious() {
do {
try ObjC.catchException {
devilMayCare()
}
}
catch {
print("An NSException was thrown:", error)
assertionFailure()
}
}
Of course, if you're writing Swift in a Unix environment, you still have access to the terrifying world of Unix interrupts. You can use Grand Central Dispatch to both throw and catch these. And, as you desire, there's no way for the compiler to guard against them being thrown.
import Dispatch // or Foundation
signal(SIGINT, SIG_IGN) // // Make sure the signal does not terminate the application.
let sigintSource = DispatchSource.makeSignalSource(signal: SIGINT, queue: .main)
sigintSource.setEventHandler {
print("Got SIGINT")
// ...
exit(0)
}
sigintSource.resume()
exit is in this family if your goal is to trap it and read its code.

How to add values to existing enum

I use the Alamofire swift library to do some networking in my App.
I'm currently building my own ResponseSerializer.
According to Alamofire's docs you throw errors in the serialization process like this
let failureReason = "Data could not be serialized. Input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(data, error)
So my thought was to use this method to throw some more errors not related to Alamofire that can appear in my serialization. For example if there is a problem on the server-side, the response includes an error description as a string.
I want to add some custom error codes like .ServerErrorInJson that I can use in the errorWithCode(Alamofire.Error.Code, String) -> NSError method.
Is there a way I can add those cases to the enum Alamofire.Error.Code?

Swift 2: Invalid conversion from throwing function of type to non-throwing function

I have some (ugly) self-written code ported to Swift2 and got this error message in a lambda function:
What I didn't understand is, that I handle the whole code with the error throwing function JSONObjectWithData and catch the error. I throw nothing in the code. Nevertheless the compiler means that I am throwing an error.
I need to understand this behavior. Please be kind because I know that I have to improve my code to make full use of the new error handling concept in swift2.
Thank you very much in advance.
This was fast. I have figured the solution for my problem out with a little help of this article:
http://www.hackingwithswift.com/new-syntax-swift-2-error-handling-try-catch
you have to put a general catch clause at the end of the code because the catch of NSError alone is not sufficient.
catch let error as NSError
{
failure(error: error)
return
}
// this is important -->
catch
{
}
I think the best way forward is to change your failure function signature to take an ErrorType. Then just
catch let error {
failure(error: error)
}
will do.

How to correctly handle errors on iPhone

I have a question about error/exception handling on the iPhone.
I've read the documentation about Exception, and it seems exceptions can be used only for exceptional situations.
Does it mean you can't use them like in java ?
for example, I'm trying to write a use case controller for my application. I've got a few examples in Java from previous projects in that language, that use exceptions in case of errors.
the question put simply is: can I follow the example I've got in Java, and "translate" it in Objective-C (and use Objective-C exceptions) or is there a better way to do that ?
here is the code I would like to make objective-c friendly:
public void addPerformance(Perfomance perf) {
//do some preparation
...
//execute the usecase
executor(new AddPerformance(perf));
}
private void executor(Usecase usecase) {
try {
UnitOfWorkServices.INSTANCE.bizTransactionStart();
usecase.execute();
UnitOfWorkServices.INSTANCE.bizTransactionCommit();
} catch (RealException re) {
UnitOfWorkServices.INSTANCE.bizTransactionEscape();
throw re;
} catch (Exception e) {
UnitOfWorkServices.INSTANCE.bizTransactionEscape();
throw new FatalException(this.getClass().getName() + " / executor("
+ usecase.getClass().getSimpleName() + ")", e,
"APPXCP_006_UNEXPECTED_EXCEPTION",
"\n\t |*| : Unexpected exception translated into FatalException");
} finally {
UnitOfWorkServices.INSTANCE.bizTransactionEnd();
}
}
All the exceptions are meant to be caught by the UI to display an error message.
thanks for your help,
Michael
In general, yes, you can translate your try/catch logic into a comparable construct in Objective-C and get comparable results. Though do be careful with that throw re; line, as an uncaught exception on the iPhone will crash your app.
More to the point however, the standard pattern used by the iPhone SDK and other commonly used libraries is that instead of throwing exceptions, API methods that may fail for a variety of reasons return a boolean value indicating whether or not the operation was successful, and accept as a parameter a reference to an NSError pointer that is used in the event of an error to provide the caller with specific details about what went wrong. So in this case, your code might translate to something more like:
NSError* error = nil;
[[UnitOfWorkServices sharedInstance] bizTransactionStart];
bool success = [usecase execute:&error];
if (success) {
[[UnitOfWorkServices sharedInstance] bizTransactionCommit];
}
else if (error) {
int code = [error code];
[[UnitOfWorkServices sharedInstance] bizTransactionEscape];
if (code == MY_MINOR_ERROR_CODE) {
//do something
}
else if (code == MY_FATAL_ERROR_CODE) {
//do something else
}
else {
//handle unexpected error type(s)
}
}
else {
//operation failed with no specific error details
[[UnitOfWorkServices sharedInstance] bizTransactionEscape];
//handle generic error
}
[[UnitOfWorkServices sharedInstance] bizTransactionEnd];
You can use exceptions just as you would use exceptions in Java, but it is not the recomended way to do it. Translating Java to Objective-C that way works, and most people will understand your intentions. But the result will be just as bad as if you tried to translate English to German using a dictionary; the spelling is perfect, but the grammar and context is completely wrong.
Understanding the "Cocoa way" to handle errors and exceptions will help you create more robust iOS applications, and give you a better understanding of how to use the official and third party API:s.
I have written a longer blog post on the topic here: http://blog.jayway.com/2010/10/13/exceptions-and-errors-on-ios/
Lets recap the important parts:
Exceptions should only be used for programming errors, and fatal stuff that you can not recover from. For example out of bounds exception, if you called with the wrong argument the first time your app is in error, retrying 100 times will not fix your app, only keep failing. The exceptions are exceptions to normal workflow and there to help you fix all bugs before you ship the app to end users.
NSError instances is what you use for errors caused by the users, and stuff that your application can recover from. For example IO-errors, the user might want to re-connect to the network, try a new password, and try again. This is for errors that you can predict, and that the app or the user can do something about.