Error-Handling in Swift-Language
我没有读过太多关于Swift的信息,但我注意到的一件事是没有例外。
那么他们如何在Swift中进行错误处理呢? 有没有人发现任何与错误处理相关的内容?
斯威夫特2和3
Swift 2中的情况发生了一些变化,因为有一种新的错误处理机制,它与异常更相似但细节不同。
1.表明错误的可能性
如果函数/方法想要指示它可能抛出错误,它应该包含
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模式匹配的所有强大功能,因此您在这里非常灵活。
如果您正在使用标有
1 2 3 4 | func fulfill(quest: Quest) throws { let dragon = try summonDefaultDragon() quest.ride(dragon) } |
或者,您可以使用
1 | let dragonOrNil = try? summonDefaultDragon() |
这样,如果发生任何错误,您将获得返回值或nil。使用这种方式,您不会得到错误对象。
这意味着您还可以将
1 | if let dragon = try? summonDefaultDragon() |
要么
1 | guard let dragon = try? summonDefaultDragon() else { ... } |
最后,您可以确定您知道实际上不会发生错误(例如,因为您已经检查过先决条件)并使用
1 | let dragon = try! summonDefaultDragon() |
如果函数实际抛出错误,那么您将在应用程序中收到运行时错误,应用程序将终止。
3.抛出错误
为了抛出错误,你可以像这样使用throw关键字
1 | throw DragonError.dragonIsMissing |
您可以抛出符合
1 2 3 4 5 | enum DragonError: ErrorType { case dragonIsMissing case notEnoughMana(requiredMana: Int) ... } |
新的Swift 2和3错误机制与Java / C#/ C ++样式异常之间的主要区别如下:
作为一种额外的合成糖来缓解错误处理,还有两个概念
斯威夫特1
运行时错误:
正如Leandros建议处理运行时错误(如网络连接问题,解析数据,打开文件等),你应该像在ObjC中那样使用
正在使用的另一种常见模式是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") }) |
仍然是故障块经常收到
程序员错误:
对于程序员错误(如数组元素的越界访问,传递给函数调用的无效参数等),您在ObjC中使用了异常。 Swift语言似乎没有任何语言支持异常(如
1 | NSException(name:"SomeName", reason:"SomeReason", userInfo: nil).raise() |
虽然您可以选择在ObjC代码中捕获异常,但您无法在纯Swift中捕获它们。
问题是你是否应该抛出程序员错误的异常,或者更确切地说使用Apple在语言指南中建议的断言。
好。
2015年6月9日更新 - 非常重要
Swift 2.0附带
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。 sub>
示例:(摘自本书)
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的方法。
在开发过程中,您可以使用
经典的
简要示例:
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中,
例如,在CoreData获取数据方法中,而不是将
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的新功能。它使用
请参阅官方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") } |