Error-Handling in Swift-Language
Asked Answered
B

13

195

I haven't read too much into Swift but one thing I noticed is that there are no exceptions. So how do they do error handling in Swift? Has anyone found anything related to error-handling?

Bigner answered 3/6, 2014 at 8:35 Comment(5)
I found error messages just like with Obj-C :oAcquit
@Acquit the good old segfault way?Bigner
Created an NSTimer in Swift and when I misspelled the function it crashed and gave me an error saying it could not find the method :)Acquit
You can add try-catch support for Swift by following the instructions in this article: medium.com/@_willfalcon/adding-try-catch-to-swift-71ab27bcb5b8Dani
@Bigner How do you handle a segfault in Swift? I don't think it's possible as of now, which sadly makes some errors unrecoverableSolo
R
153

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.

Reproduce answered 4/6, 2014 at 5:56 Comment(7)
"network connectivity problems" and "opening files" using the Cocoa APIs (NSFileHandle) can throw exceptions that need to be caught. Without exceptions in Swift, you need to implement this part of your program in Objective-C or perform all your work using the BSD C APIs (both of which are poor work-arounds). See the documentation for NSFileHandle.writeData for more... developer.apple.com/library/ios/documentation/Cocoa/Reference/…:Elatia
Again, no exception handling means two-stage object construction with all its inherent problems. See stroustrup.com/except.pdf.Surveyor
the fatalError(...) is the same as well.Eugeneeugenia
As much as I like Swift, I think this is a catastrophic choice, and having had a taste of some of the consequences, they are playing with fire with this omission...Latricialatrina
Hmmm. For the most part, it is also considered good practice to avoid checked exceptions in modern Java. Some older standard libs use checked exceptions, but frameworks like Spring go to the effort of remapping these to unchecked RuntimeExceptions.Adigun
@JasperBlues: is there any particular reason for that?Reproduce
Yes, checked exceptions are now used sparingly, because it was found that forcing the programmer to catch exceptions they have little hope of recovering from pollutes code in single responsibility principle breaking ways. A domain level class doesn't want to have to deal with infrastructure layer exceptions. So now, non-checked exceptions tend to be favored, and the interested party can catch them, if appropriate. . Checked = definitely recoverable. Unchecked = non/potentially recoverable.Adigun
M
70

Update June 9th 2015 - Very important

Swift 2.0 comes with try, throw, and catch keywords and the most exciting is:

Swift automatically translates Objective-C methods that produce errors into methods that throw an error according to Swift's native error handling functionality.

Note: Methods that consume errors, such as delegate methods or methods that take a completion handler with an NSError object argument, do not become methods that throw when imported by Swift.

Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C (Swift 2 Prerelease).” iBooks.

Example: (from the book)

NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"];
NSError *error = nil;
BOOL success = [fileManager removeItemAtURL:URL error:&error];
if (!success && error){
    NSLog(@"Error: %@", error.domain);
}

The equivalent in swift will be:

let fileManager = NSFileManager.defaultManager()
let URL = NSURL.fileURLWithPath("path/to/file")
do {
    try fileManager.removeItemAtURL(URL)
} catch let error as NSError {
    print ("Error: \(error.domain)")
}

Throwing an Error:

*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]

Will be automatically propagated to the caller:

throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)

From Apple books, The Swift Programming Language it's seems errors should be handle using enum.

Here is an example from the book.

enum ServerResponse {
    case Result(String, String)
    case Error(String)
}

let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")

switch success {
case let .Result(sunrise, sunset):
    let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
    let serverResponse = "Failure...  \(error)"
}

From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/br/jEUH0.l

Update

From Apple news books, "Using Swift with Cocoa and Objective-C". Runtime exceptions not occur using swift languages, so that's why you don't have try-catch. Instead you use Optional Chaining.

Here is a stretch from the book:

For example, in the code listing below, the first and second lines are not executed because the length property and the characterAtIndex: method do not exist on an NSDate object. The myLength constant is inferred to be an optional Int, and is set to nil. You can also use an if–let statement to conditionally unwrap the result of a method that the object may not respond to, as shown on line three

let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
    println("Found \(fifthCharacter) at index 5")
}

Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks. https://itun.es/br/1u3-0.l


And the books also encourage you to use cocoa error pattern from Objective-C (NSError Object)

Error reporting in Swift follows the same pattern it does in Objective-C, with the added benefit of offering optional return values. In the simplest case, you return a Bool value from the function to indicate whether or not it succeeded. When you need to report the reason for the error, you can add to the function an NSError out parameter of type NSErrorPointer. This type is roughly equivalent to Objective-C’s NSError **, with additional memory safety and optional typing. You can use the prefix & operator to pass in a reference to an optional NSError type as an NSErrorPointer object, as shown in the code listing below.

var writeError : NSError?
let written = myString.writeToFile(path, atomically: false,
    encoding: NSUTF8StringEncoding,
    error: &writeError)
if !written {
    if let error = writeError {
        println("write failure: \(error.localizedDescription)")
    }
}

Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks. https://itun.es/br/1u3-0.l

Messing answered 12/6, 2014 at 13:2 Comment(2)
For the last statement, it should be : do { try myString.writeToFile(path, atomically: true, encoding: NSUTF8StringEncoding) }catch let error as NSError { print(error) }Fundamental
@Jacky Yes, that is true for swift 2.0 although this answer was wrote before the release of swift 2.0, I updated the answer to show the new way handle errors in swift 2.0. I was thinking in letting this way for reference, but I will consider update the entire answer to use only swift 2.0Messing
K
13

There are no Exceptions in Swift, similar to Objective-C's approach.

In development, you can use assert to catch any errors which might appear, and need to be fixed before going to production.

The classic NSError approach isn't altered, you send an NSErrorPointer, which gets populated.

Brief example:

var error: NSError?
var contents = NSFileManager.defaultManager().contentsOfDirectoryAtPath("/Users/leandros", error: &error)
if let error = error {
    println("An error occurred \(error)")
} else {
    println("Contents: \(contents)")
}
Kelleykelli answered 3/6, 2014 at 8:44 Comment(12)
This rises a two questions: what happens when ObjC code we call from Swift actually throws an exception, and whether NSError is our universal error object like in ObjC?Reproduce
I have to test the first later, but for the second/ Yes, it is.Kelleykelli
Is it just a fact of life with Swift that initializers don't or can't fail?Surveyor
The exception handling appears rather dirtySikh
It's similar to Go's approach and I guess they're constrained by the Obj-C interoperability. At least it forces you to deal with errors then and thereCornelia
don't you need to do var error: NSError? = NSError() ?Superiority
Go at least has a builtin error type and "exception like" functionality with panics.Leaky
Yeah, who needs exceptions when you can just crash? Or put an NSError** as argument in all the functions you declare? so that every f();g(); becomes f(&err);if(err) return;g(&err);if(err) return; for the first month, then it just become f(nil);g(nil);hopeToGetHereAlive();Ardis
@Kelleykelli The way you check for errors is a classic bug when programming in Objective-C. Are there differences in Swift regarding error-checking with Obj-C APIs which expose an error parameter?Jonas
@MDJ - right now my Obj-C code throws exception and all execution in swift is halted ... no errors, no logs, no crash ... it just skips the block and continues the next oneHobbs
This answer is both outdated (Swift now supports exceptions) and wrong (Objective-C does support exceptions.Petepetechia
@RogerNolan Objective-C has exceptions, but you shouldn't use them for catching runtime errors, from where you can recover.Kelleykelli
K
12

The recommended 'Swift Way' is:

func write(path: String)(#error: NSErrorPointer) -> Bool { // Useful to curry error parameter for retrying (see below)!
    return "Hello!".writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: error)
}

var writeError: NSError?
let written = write("~/Error1")(error: &writeError)
if !written {
    println("write failure 1: \(writeError!.localizedDescription)")
    // assert(false) // Terminate program
}

However I prefer try/catch as I find it easier to follow because it moves the error handling to a separate block at the end, this arrangement is sometimes called "Golden Path". Lucky you can do this with closures:

TryBool {
    write("~/Error2")(error: $0) // The code to try
}.catch {
    println("write failure 2: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

Also it is easy to add a retry facility:

TryBool {
    write("~/Error3")(error: $0) // The code to try
}.retry {
    println("write failure 3 on try \($1 + 1): \($0!.localizedDescription)")
    return write("~/Error3r")  // The code to retry
}.catch {
    println("write failure 3 catch: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

The listing for TryBool is:

class TryBool {
    typealias Tryee = NSErrorPointer -> Bool
    typealias Catchee = NSError? -> ()
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return self.retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) {
        var error: NSError?
        for numRetries in 0...retries { // First try is retry 0
            error = nil
            let result = tryee(&error)
            if result {
                return
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        catchee(error)
    }
}

You can write a similar class for testing an Optional returned value instead of Bool value:

class TryOptional<T> {
    typealias Tryee = NSErrorPointer -> T?
    typealias Catchee = NSError? -> T
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) -> T {
        var error: NSError?
        for numRetries in 0...retries {
            error = nil
            let result = tryee(&error)
            if let r = result {
                return r
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        return catchee(error)
    }
}

The TryOptional version enforces a non-Optional return type that makes subsequent programming easier, e.g. 'Swift Way:

struct FailableInitializer {
    init?(_ id: Int, error: NSErrorPointer) {
        // Always fails in example
        if error != nil {
            error.memory = NSError(domain: "", code: id, userInfo: [:])
        }
        return nil
    }
    private init() {
        // Empty in example
    }
    static let fallback = FailableInitializer()
}

func failableInitializer(id: Int)(#error: NSErrorPointer) -> FailableInitializer? { // Curry for retry
    return FailableInitializer(id, error: error)
}

var failError: NSError?
var failure1Temp = failableInitializer(1)(error: &failError)
if failure1Temp == nil {
    println("failableInitializer failure code: \(failError!.code)")
    failure1Temp = FailableInitializer.fallback
}
let failure1 = failure1Temp! // Unwrap

Using TryOptional:

let failure2 = TryOptional {
    failableInitializer(2)(error: $0)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

let failure3 = TryOptional {
    failableInitializer(3)(error: $0)
}.retry {
    println("failableInitializer failure, on try \($1 + 1), code: \($0!.code)")
    return failableInitializer(31)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

Note auto-unwrapping.

Kelle answered 5/11, 2014 at 4:6 Comment(0)
J
8

Edit: Although this answer works, it is little more than Objective-C transliterated into Swift. It has been made obsolete by changes in Swift 2.0. Guilherme Torres Castro's answer above is a very good introduction to the preferred way of handling errors in Swift. VOS

It took a bit of figuring it out but I think I've sussed it. It seems ugly though. Nothing more than a thin skin over the Objective-C version.

Calling a function with an NSError parameter...

var fooError : NSError ? = nil

let someObject = foo(aParam, error:&fooError)

// Check something was returned and look for an error if it wasn't.
if !someObject {
   if let error = fooError {
      // Handle error
      NSLog("This happened: \(error.localizedDescription)")
   }
} else {
   // Handle success
}`

Writing the function that takes an error parameter...

func foo(param:ParamObject, error: NSErrorPointer) -> SomeObject {

   // Do stuff...

   if somethingBadHasHappened {
      if error {
         error.memory = NSError(domain: domain, code: code, userInfo: [:])
      }
      return nil
   }

   // Do more stuff...
}
Joeljoela answered 9/6, 2014 at 21:18 Comment(1)
D
6

Basic wrapper around objective C that gives you the try catch feature. https://github.com/williamFalcon/SwiftTryCatch

Use like:

SwiftTryCatch.try({ () -> Void in
        //try something
     }, catch: { (error) -> Void in
        //handle error
     }, finally: { () -> Void in
        //close resources
})
Dani answered 10/10, 2014 at 18:30 Comment(4)
Good idea. But who decide to use this must keep in mind that objects allocated in the try block are not deallocated when an exception is thrown. This can cause problems of zombie objects and every use of RAII is compromised (auto-unlock, auto-sql-commit, auto-sql-rollback...). Maybe c++ could help us with some form of "runAtExit"?Ardis
Update: i just found that there is a flag in clang to enable the release of objects at exception throwing: -fobjc-arc-exceptions . I must try if it still work with the wrapped version (i think it should)Ardis
If you use this option be aware that the code size increases since the compiler has to generate semi-exception-safe code. Also: Relying on such a compiler feature may not be the best idea. Exceptions are for programmer errors only so turing on that compiler option just to save a little bit of memory during development is not worth it. If you have exceptions in you production code you should deal with the thing causing those exceptions in the first place.Folsom
There could be situations out of your control. For instance, parsing json in the wrong format.Dani
D
4

As Guilherme Torres Castro said, in Swift 2.0, try, catch, do can be used in the programming.

For example, In CoreData fetch data method, instead of put &error as a parameter into the managedContext.executeFetchRequest(fetchRequest, error: &error), now we only need to use use managedContext.executeFetchRequest(fetchRequest) and then handle the error with try, catch (Apple Document Link)

do {
   let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
   if let results = fetchedResults{
      people = results
   }
} catch {
   print("Could not fetch")
}

If you have already download the xcode7 Beta. Try to search throwing errors in Documentations and API Reference and choose the first showing result, it gives a basic idea what can be done for this new syntax. However, fully documentation is not post for many APIs yet.

More fancy Error Handling techniques can be found in

What's New in Swift (2015 Session 106 28m30s)

Dusk answered 10/6, 2015 at 15:28 Comment(0)
C
3

This is an update answer for swift 2.0. I am looking forward feature rich Error handling model like in java. Finally, they announced the good news. here

Error handling model: The new error handling model in Swift 2.0 will instantly feel natural, with familiar try, throw, and catch keywords. Best of all, it was designed to work perfectly with the Apple SDKs and NSError. In fact, NSError conforms to a Swift’s ErrorType. You’ll definitely want to watch the WWDC session on What’s New in Swift to hear more about it.

e.g :

func loadData() throws { }
func test() {
do {
    try loadData()
} catch {
    print(error)
}}
Connell answered 9/6, 2015 at 5:4 Comment(0)
A
2

Error handling is a new feature of Swift 2.0. It uses the try, throw and catch keywords.

See the Apple Swift 2.0 announcement on the official Apple Swift blog

Albin answered 9/6, 2015 at 14:2 Comment(0)
N
2

Starting with Swift 2, as others have already mentioned, error handling is best accomplished through the use of do/try/catch and ErrorType enums. This works quite well for synchronous methods, but a little cleverness is required for asynchronous error handling.

This article has a great approach to this problem:

https://jeremywsherman.com/blog/2015/06/17/using-swift-throws-with-completion-callbacks/

To summarize:

// create a typealias used in completion blocks, for cleaner code
typealias LoadDataResult = () throws -> NSData

// notice the reference to the typealias in the completionHandler
func loadData(someID: String, completionHandler: LoadDataResult -> Void)
    {
    completionHandler()
    }

then, the call to the above method would be as follows:

self.loadData("someString",
    completionHandler:     
        { result: LoadDataResult in
        do
            {
            let data = try result()
            // success - go ahead and work with the data
            }
        catch
            {
            // failure - look at the error code and handle accordingly
            }
        })

This seems a bit cleaner than having a separate errorHandler callback passed to the asynchronous function, which was how this would be handled prior to Swift 2.

Nevanevada answered 24/9, 2015 at 16:40 Comment(0)
K
1

Nice and simple lib to handle exception: TryCatchFinally-Swift

Like a few others it wraps around the objective C exception features.

Use it like this:

try {
    println("  try")
}.catch { e in
    println("  catch")
}.finally {
    println("  finally")
}
Khabarovsk answered 1/4, 2015 at 12:39 Comment(2)
I have added a sample :)Khabarovsk
It's probably worth mentioning the authors opinion: "Warning: This is a hack for fun and evil. Resist the temptation to use it."Saurian
C
1
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: 26)
    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 ")
}

Change age in try checkValidAgeForGovernmentJob(age: 26)

Out Put You are overrage for government job

Coom answered 3/2, 2022 at 6:15 Comment(0)
R
0

What I have seen is that because of the nature of the device you don't want to be throwing a bunch of cryptic error handling messages at the user. That is why most functions return optional values then you just code to ignore the optional. If a function comes back nil meaning it failed you can pop a message or whatever.

Rowena answered 9/6, 2015 at 6:17 Comment(1)
Returning a nil returns no information on the nature of the error. If an error object is returned when an error occurs then, depending on the error, the programmer can choose to ignore it, handle it, let it bubble up or "pop a message or whatever". Knowledge is power.Framboise

© 2022 - 2024 — McMap. All rights reserved.