Error-Handling in Swift-Language

Error-Handling in Swift-Language

Swift 2 & 3

Things have changed a bit in Swift 2, as there is a new error-handling mechanism, that is somewhat more similar to exceptions but different in detail.

1. Indicating error possibility

If function/method wants to indicate that it may throw an error, it should contain throws keyword like this

func summonDefaultDragon() throws -> Dragon

Note: there is no specification for type of error the function actually can throw. This declaration simply states that the function can throw an instance of any type implementing ErrorType or is not throwing at all.

2. Invoking function that may throw errors

In order to invoke function you need to use try keyword, like this

try summonDefaultDragon()

this line should normally be present do-catch block like this

do {
let dragon = try summonDefaultDragon()
} catch DragonError.dragonIsMissing {
// Some specific-case error-handling
} catch DragonError.notEnoughMana(let manaRequired) {
// Other specific-case error-handlng
} catch {
// Catch all error-handling
}

Note: catch clause use all the powerful features of Swift pattern matching so you are very flexible here.

You may decided to propagate the error, if your are calling a throwing function from a function that is itself marked with throws keyword:

func fulfill(quest: Quest) throws {
let dragon = try summonDefaultDragon()
quest.ride(dragon)
}

Alternatively, you can call throwing function using try?:

let dragonOrNil = try? summonDefaultDragon()

This way you either get the return value or nil, if any error occurred. Using this way you do not get the error object.

Which means that you can also combine try? with useful statements like:

if let dragon = try? summonDefaultDragon()

or

guard let dragon = try? summonDefaultDragon() else { ... }

Finally, you can decide that you know that error will not actually occur (e.g. because you have already checked are prerequisites) and use try! keyword:

let dragon = try! summonDefaultDragon()

If the function actually throws an error, then you'll get a runtime error in your application and the application will terminate.

3. Throwing an error

In order to throw an error you use throw keyword like this

throw DragonError.dragonIsMissing

You can throw anything that conforms to ErrorType protocol. For starters NSError conforms to this protocol but you probably would like to go with enum-based ErrorType which enables you to group multiple related errors, potentially with additional pieces of data, like this

enum DragonError: ErrorType {
case dragonIsMissing
case notEnoughMana(requiredMana: Int)
...
}

Main differences between new Swift 2 & 3 error mechanism and Java/C#/C++ style exceptions are follows:

  • Syntax is a bit different: do-catch + try + defer vs traditional try-catch-finally syntax.
  • Exception handling usually incurs much higher execution time in exception path than in success path. This is not the case with Swift 2.0 errors, where success path and error path cost roughly the same.
  • All error throwing code must be declared, while exceptions might have been thrown from anywhere. All errors are "checked exceptions" in Java nomenclature. However, in contrast to Java, you do not specify potentially thrown errors.
  • Swift exceptions are not compatible with ObjC exceptions. Your do-catch block will not catch any NSException, and vice versa, for that you must use ObjC.
  • Swift exceptions are compatible with Cocoa NSError method conventions of returning either false (for Bool returning functions) or nil (for AnyObject returning functions) and passing NSErrorPointer with error details.

As an extra syntatic-sugar to ease error handling, there are two more concepts

  • deferred actions (using defer keyword) which let you achieve the same effect as finally blocks in Java/C#/etc
  • guard statement (using guard keyword) which let you write little less if/else code than in normal error checking/signaling code.

Swift 1

Runtime errors:

As Leandros suggests for handling runtime errors (like network connectivity problems, parsing data, opening file, etc) you should use NSError like you did in ObjC, because the Foundation, AppKit, UIKit, etc report their errors in this way. So it's more framework thing than language thing.

Another frequent pattern that is being used are separator success/failure blocks like in AFNetworking:

var sessionManager = AFHTTPSessionManager(baseURL: NSURL(string: "yavin4.yavin.planets"))
sessionManager.HEAD("/api/destoryDeathStar", parameters: xwingSquad,
success: { (NSURLSessionDataTask) -> Void in
println("Success")
},
failure:{ (NSURLSessionDataTask, NSError) -> Void in
println("Failure")
})

Still the failure block frequently received NSError instance, describing the error.

Programmer errors:

For programmer errors (like out of bounds access of array element, invalid arguments passed to a function call, etc) you used exceptions in ObjC. Swift language does not seem to have any language support for exceptions (like throw, catch, etc keyword). However, as documentation suggests it is running on the same runtime as ObjC, and therefore you are still able to throw NSExceptions like this:

NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()

You just cannot catch them in pure Swift, although you may opt for catching exceptions in ObjC code.

The questions is whether you should throw exceptions for programmer errors, or rather use assertions as Apple suggests in the language guide.

Swift Error handling best practices

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.

  1. Error Protocol Apple Developer Documentation
  2. Error Handling - Apple's Swift Programming Language Guide
  3. 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.

Error Handling an Objective-C framework in Swift Language

This is a very good question! It's a legitimate issue that needs to be addressed. Unfortunately, you cannot achieve this at the moment. Here is why:

Swift exception handling has nothing to do with Exceptions. In fact, you cannot catch NSException, which is what's being raised in the Obj-C side. Swift catches NSError which is practically (in Swift) an Enum. Long story short, there are two patterns for handling errors in Objective-C: 1. raising NSException 2. Returning NSError

As I explained, you cannot handle errors raised using the first approach. So, you literally, have to modify your Objective-C code to comply with the second approach, using NSError.

Some good links:

https://www.bignerdranch.com/blog/error-handling-in-swift-2/
https://forums.developer.apple.com/thread/7582

Why the swift throws exception despite try?

try? does not catch exceptions. It catches thrown errors. Those are different things in Swift. Exceptions are at the Objective-C level and cannot be caught by Swift at all (they can't be safely caught in ObjC in most cases either, but that's a different discussion).

The solution in this case is to use JSONEncoder rather than JSONSerialization. JSONEncoder is a pure-Swift system. JSONSerialization is bridged from ObjC.

let body = try? JSONEncoder().encode([data])

See Handling Errors for more information:

Error handling in Swift resembles exception handling in other languages, with the use of the try, catch and throw keywords. Unlike exception handling in many languages—including Objective-C—error handling in Swift does not involve unwinding the call stack, a process that can be computationally expensive. As such, the performance characteristics of a throw statement are comparable to those of a return statement.

If you want to use JSONSerialization, it's important to recognize that it is a programming error to call it this way. The exception is intended to crash the program (even in ObjC). The correct way to write this code is:

if JSONSerialization.isValidJSONObject([data]), // <=== first, check it is valid
let body = try? JSONSerialization.data(withJSONObject: [data]) {
print("success")
} else {
print("unable to make body for call")
}

See the docs for more information:

If obj will not produce valid JSON, an exception is thrown. This exception is thrown prior to parsing and represents a programming error, not an internal error. You should check whether the input will produce valid JSON before calling this method by using isValidJSONObject(_:).

The thrown error from JSONSerialization is only to indicate an internal error in the serializer, not an attempt to encode an invalid object:

error

If an internal error occurs, upon return contains an NSError object with code NSPropertyListWriteInvalidError that describes the problem.

Swift Error Handling: Authentication System

Modify the authenticate function as:

func authenticate(user:String,pass:String) throws -> Bool{
print(user)
print(pass)
guard authDict[user] == pass else {
throw CustomErrors.AuthenticationError(message: "Invalid Credentials")
}
return true
}

Now the function throws a CustomErrors error if user and pass do not match those stored in the authDict Dictionary.

Best practice for Swift methods that can return or error

To add an answer to this question (five years later), there’s a dedicated Result type for this exact scenario. It can return the type you want on success, or type an error on failure.

It does mean re-factoring some code to instead accept a completion handler, and then enumerating over the result in that callback:

class SecurityService {
static func loginWith(email: String, password: String, completionHandler: @escaping (Result<User, SecurityError>) -> Void) {
// Body
}
}

Then in a handler:

securityService.loginWith(email: email, password: password) { result in
switch result {
case .success(let user):
// Do something with user
print("Authenticated as \(user.name)")
case .failure(let error):
// Do something with error
print(error.localizedDescription)
}
}

What is Good Practice 'Error Handling' for Swift 4?

You're heading for a lot of trouble if you're going to use ´try?´ for error handling, especially in a situation as crucial as saving your data.

Do yourself a big favour and use a proper do/try/catch (or re-throwing) as your standard way of handling error and only use try? or try! in very specific situations when you are aware of the consequences. It's more code to write but as soon as you have some kind of issue you'll appreciate the extra effort.

do {
try coreData.save()
} catch {
print("Unable to save Managed Object Context")
print("\(error), \(error.localizedDescription)")
}


Related Topics



Leave a reply



Submit