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 traditionaltry-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 eitherfalse
(forBool
returning functions) ornil
(forAnyObject
returning functions) and passingNSErrorPointer
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 let
s and throw
s.
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.
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
How Does One Generate a Random Number in Swift
Instantiated Optional Variable Shows as Nil in Xcode Debugger
Setting Device Orientation in Swift Ios
How to Convert Unix Epoch Time to Date and Time in iOS Swift
Whither Dispatch_Once in Swift 3
How to Unwrap Double Optionals
Extend Array Types Using Where Clause in Swift
How to Append Elements into a Dictionary in Swift
Inout Parameter in Async Callback Does Not Work as Expected
Noop For Swift'S Exhaustive Switch Statements
How to Print the Type or Class of a Variable in Swift
How to Create a Segue That Can Be Called from a Button That Is Created Programmatically