Swift语言中的错误处理

Error-Handling in Swift-Language

我没有读过太多关于Swift的信息,但我注意到的一件事是没有例外。
那么他们如何在Swift中进行错误处理呢? 有没有人发现任何与错误处理相关的内容?


斯威夫特2和3

Swift 2中的情况发生了一些变化,因为有一种新的错误处理机制,它与异常更相似但细节不同。

1.表明错误的可能性

如果函数/方法想要指示它可能抛出错误,它应该包含throws这样的关键字

1
func summonDefaultDragon() throws -> Dragon

注意:函数实际上可以抛出的错误类型没有规范。该声明只是声明该函数可以抛出任何实现ErrorType的类型的实例,或者根本不抛出。

2.调用可能引发错误的函数

为了调用函数,你需要使用try关键字,就像这样

1
try summonDefaultDragon()

这条线通常应该像这样存在do-catch块

1
2
3
4
5
6
7
8
9
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
}

注意:catch子句使用Swift模式匹配的所有强大功能,因此您在这里非常灵活。

如果您正在使用标有throws关键字的函数调用throw函数,则可能决定传播错误:

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

或者,您可以使用try?调用throw函数:

1
let dragonOrNil = try? summonDefaultDragon()

这样,如果发生任何错误,您将获得返回值或nil。使用这种方式,您不会得到错误对象。

这意味着您还可以将try?与有用的语句结合使用,例如:

1
if let dragon = try? summonDefaultDragon()

要么

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

最后,您可以确定您知道实际上不会发生错误(例如,因为您已经检查过先决条件)并使用try!关键字:

1
let dragon = try! summonDefaultDragon()

如果函数实际抛出错误,那么您将在应用程序中收到运行时错误,应用程序将终止。

3.抛出错误

为了抛出错误,你可以像这样使用throw关键字

1
throw DragonError.dragonIsMissing

您可以抛出符合ErrorType协议的任何内容。对于初学者NSError符合此协议,但您可能希望使用基于枚举的ErrorType,这使您可以将多个相关错误分组,可能还有其他数据,例如

1
2
3
4
5
enum DragonError: ErrorType {
    case dragonIsMissing
    case notEnoughMana(requiredMana: Int)
    ...
}

新的Swift 2和3错误机制与Java / C#/ C ++样式异常之间的主要区别如下:

  • 语法略有不同:do-catch + try + defer与传统的try-catch-finally语法相比。
  • 异常处理通常会在异常路径中产生比在成功路径中高得多的执行时间。 Swift 2.0错误的情况并非如此,其中成功路径和错误路径的成本大致相同。
  • 必须声明所有错误抛出代码,而异常可能从任何地方抛出。所有错误都是Java命名法中的"已检查异常"。但是,与Java相比,您不指定可能抛出的错误。
  • Swift异常与ObjC异常不兼容。您的do-catch块不会捕获任何NSException,反之亦然,因为您必须使用ObjC。
  • Swift异常与返回false(对于Bool返回函数)或nil(对于AnyObject返回函数)并传递NSErrorPointer错误详细信息的Cocoa NSError方法约定兼容。
  • 作为一种额外的合成糖来缓解错误处理,还有两个概念

  • 延迟操作(使用defer关键字),它可以实现与Java / C#/ etc中的finally块相同的效果
  • guard语句(使用guard关键字),它可以让你写的if / else代码少于正常的错误检查/信令代码。
  • 斯威夫特1

    运行时错误:

    正如Leandros建议处理运行时错误(如网络连接问题,解析数据,打开文件等),你应该像在ObjC中那样使用NSError,因为Foundation,AppKit,UIKit等以这种方式报告错误。所以它比语言事物更具框架性。

    正在使用的另一种常见模式是AFNetworking中的分隔符成功/失败块:

    1
    2
    3
    4
    5
    6
    7
    8
    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")
        })

    仍然是故障块经常收到NSError实例,描述错误。

    程序员错误:

    对于程序员错误(如数组元素的越界访问,传递给函数调用的无效参数等),您在ObjC中使用了异常。 Swift语言似乎没有任何语言支持异常(如throwcatch等关键字)。但是,正如文档所示,它在与ObjC相同的运行时运行,因此您仍然能够像这样抛出NSExceptions

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

    虽然您可以选择在ObjC代码中捕获异常,但您无法在纯Swift中捕获它们。

    问题是你是否应该抛出程序员错误的异常,或者更确切地说使用Apple在语言指南中建议的断言。

    好。


    2015年6月9日更新 - 非常重要

    Swift 2.0附带trythrowcatch关键字,最令人兴奋的是:

    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.

    摘录自:Apple Inc."将Swift与Cocoa和Objective-C一起使用(Swift 2 Prerelease)。"iBooks。

    示例:(摘自本书)

    1
    2
    3
    4
    5
    6
    7
    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);
    }

    swift中的等价物将是:

    1
    2
    3
    4
    5
    6
    7
    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)")
    }

    抛出错误:

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

    将自动传播给调用者:

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

    从Apple书籍,Swift编程语言来看,似乎应该使用枚举来处理错误。

    这是本书的一个例子。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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)"
    }

    来自:Apple Inc."The Swift Programming Language。"iBooks。 https://itun.es/br/jEUH0.l

    更新

    来自Apple新闻书,"使用Swift与Cocoa和Objective-C"。使用swift语言不会发生运行时异常,这就是为什么你没有try-catch。而是使用可选链接。

    这是书中的一段:

    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

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

    摘自:Apple Inc."将Swift与Cocoa和Objective-C一起使用。"iBooks。 https://itun.es/br/1u3-0.l

    书籍还鼓励你使用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.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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)")
        }
    }

    摘自:Apple Inc."将Swift与Cocoa和Objective-C一起使用。"iBooks。 https://itun.es/br/1u3-0.l


    Swift中没有Exceptions,类似于Objective-C的方法。

    在开发过程中,您可以使用assert来捕获可能出现的任何错误,并且需要在进入生产之前进行修复。

    经典的NSError方法没有改变,你发送一个NSErrorPointer,它会被填充。

    简要示例:

    1
    2
    3
    4
    5
    6
    7
    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)")
    }


    推荐的'Swift Way'是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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
    }

    但是我更喜欢try / catch,因为我发现它更容易理解,因为它将错误处理移动到最后一个单独的块,这种安排有时被称为"黄金路径"。幸运的是你可以用闭包来做到这一点:

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

    还可以轻松添加重试功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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
    }

    TryBool的列表是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    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)
        }
    }

    您可以编写一个类似的类来测试Optional返回值而不是Bool值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    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)
        }
    }

    TryOptional版本强制执行非可选的返回类型,使后续编程更容易,例如'快捷方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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

    使用TryOptional:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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
    }

    注意自动解包。


    编辑:虽然这个答案有效,但它只是将Objective-C音译为Swift。它已被Swift 2.0的变化所淘汰。 Guilherme Torres Castro上面的回答是对Swift中处理错误的首选方法的一个非常好的介绍。 VOS

    我花了一些时间来搞清楚,但我想我已经怀疑了。虽然看起来很难看。 Objective-C版本只不过是薄薄的皮肤。

    使用NSError参数调用函数...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
    }`

    编写带有错误参数的函数...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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...
    }


    目标C的基本包装,为您提供try catch功能。
    https://github.com/williamFalcon/SwiftTryCatch

    使用如下:

    1
    2
    3
    4
    5
    6
    7
    SwiftTryCatch.try({ () -> Void in
            //try something
         }, catch: { (error) -> Void in
            //handle error
         }, finally: { () -> Void in
            //close resources
    })


    正如Guilherme Torres Castro所说,在Swift 2.0中,trycatchdo可用于编程。

    例如,在CoreData获取数据方法中,而不是将&error作为参数放入managedContext.executeFetchRequest(fetchRequest, error: &error),现在我们只需要使用use managedContext.executeFetchRequest(fetchRequest)然后使用trycatch处理错误(Apple Document链接)

    1
    2
    3
    4
    5
    6
    7
    8
    do {
       let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
       if let results = fetchedResults{
          people = results
       }
    } catch {
       print("Could not fetch")
    }

    如果您已经下载了xcode7 Beta。尝试在Documentations和API Reference中搜索抛出错误并选择第一个显示结果,它提供了一个基本的想法,可以为这个新语法做些什么。但是,完整的文档还没有发布许多API。

    可以找到更多花哨的错误处理技术

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


    这是swift 2.0的更新答案。我期待功能丰富的错误处理模型,如在java中。最后,他们宣布了这个好消息。这里

    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.

    例如:

    1
    2
    3
    4
    5
    6
    7
    func loadData() throws { }
    func test() {
    do {
        try loadData()
    } catch {
        print(error)
    }}

    错误处理是Swift 2.0的新功能。它使用trythrowcatch关键字。

    请参阅官方Apple Swift博客上的Apple Swift 2.0公告


    从其他人已经提到的Swift 2开始,最好通过使用do / try / catch和ErrorType枚举来完成错误处理。这对于同步方法非常有效,但异步错误处理需要一点点聪明。

    本文对此问题有很好的解决方法:

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

    总结一下:

    1
    2
    3
    4
    5
    6
    7
    8
    // 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()
        }

    那么,对上述方法的调用如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
                }
            })

    这似乎比传递给异步函数的单独的errorHandler回调更清晰,这是在Swift 2之前处理它的方式。


    我所看到的是,由于设备的性质,您不希望在用户处抛出一堆神秘的错误处理消息。这就是为什么大多数函数返回可选值然后你只是编码忽略可选值。如果一个函数返回nil意味着它失败了你可以弹出一条消息或其他什么。


    漂亮而简单的lib来处理异常:
    TryCatchFinally - 斯威夫特

    像其他几个一样,它包含了客观的C异常功能。

    像这样使用它:

    1
    2
    3
    4
    5
    6
    7
    try {
        println("  try")
    }.catch { e in
        println("  catch")
    }.finally {
        println("  finally")
    }