Swift语言中的error handling

我没有太多的读到Swift,但是我注意到的一点是没有例外。 那么他们如何在Swift中进行error handling呢? 有没有人发现任何与error handling有关的东西?

Swift 2&3

在Swift 2中,事情已经发生了一些变化,因为有一个新的error handling机制,它与exception更类似,但细节却不尽相同。

1.指出错误的可能性

如果函数/方法想要表明它可能会抛出一个错误,它应该像这样包含throws关键字

 func summonDefaultDragon() throws -> Dragon 

注意:函数实际上可能抛出的错误types没有规定。 这个声明只是声明函数可以抛出一个实现ErrorType的实例,或者根本不抛出。

2.调用可能抛出错误的函数

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

 try summonDefaultDragon() 

这条线通常应该是像这样的现在的do-catch块

 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模式匹配的所有强大function,因此您在这里非常灵活。

如果您正在使用自身标记为throws关键字的函数调用throwing函数,则可能决定传播该错误:

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

或者,您可以使用try?调用抛出函数try?

 let dragonOrNil = try? summonDefaultDragon() 

这样,如果发生错误,您可以获得返回值或零。 使用这种方法你不会得到错误的对象。

这意味着你也可以结合try? 有用的陈述如:

 if let dragon = try? summonDefaultDragon() 

要么

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

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

 let dragon = try! summonDefaultDragon() 

如果这个函数实际上抛出一个错误,那么你的应用程序会得到一个运行时错误,应用程序将终止。

3.抛出一个错误

为了抛出一个错误,你使用像这样的抛出关键字

 throw DragonError.dragonIsMissing 

你可以抛出任何符合ErrorType协议的东西。 对于初学者来说, NSError符合这个协议,但是你可能想要使用基于枚举的ErrorType ,它使你能够将多个相关的错误分组,

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

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

  • 语法有点不同: do-catch + try + defer与传统的try-catch-finally语法。
  • exception处理通常在exceptionpath中比在成功path中招致更高的执行时间。 Swift 2.0错误并不是这种情况,成功path和错误path的成本大致相同。
  • 所有错误引发代码都必须声明,而exception可能从任何地方被抛出。 所有错误都是Java术语中的“检查exception”。 但是,与Java不同,您不指定可能抛出的错误。
  • Swiftexception与ObjCexception不兼容。 你的do-catch块不会捕获任何NSException,反之亦然,因为你必须使用ObjC。
  • Swiftexception与Cocoa NSError方法的约定是兼容的,返回false (用于Bool返回函数)或nil (用于AnyObject返回函数)并传递带有错误细节的NSErrorPointer

作为一个额外的合成糖,以减轻error handling,还有两个概念

  • 推迟的动作(使用defer关键字),它可以使你达到和Java / C#/等中的finally块一样的效果
  • guard语句(使用guard关键字),它使您可以比正常的错误检查/信号代码less写if / else代码。

斯威夫特1

运行时错误:

由于Leandrosbuild议处理运行时错误(如networking连接问题,parsing数据,打开文件等),所以应像使用ObjC一样使用NSError ,因为Foundation,AppKit,UIKit等以这种方式报告错误。 所以它比语言的东西更为框架化。

另一个常用的模式是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") }) 

仍然失败块经常收到NSError实例,描述错误。

程序员错误:

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

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

尽pipe你可以select在ObjC代码中捕获exception,但你不能在纯Swift中捕获它们。

问题在于是否应该为程序员错误抛出exception,或者像苹果在语言指南中所build议的那样使用断言。

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

Swift 2.0自带了trythrowcatch关键字,最让人兴奋的是:

Swift会自动将产生错误的Objective-C方法转换为根据Swift原生error handlingfunction抛出错误的方法。

注意:消耗错误的方法(例如委托方法或带有NSError对象参数的完成处理程序的方法)不会成为由Swift导入时抛出的方法。

摘录自:苹果公司“使用cocoa和Objective-C的Swift(Swift 2预发行)”,iBooks。

例子:(从书中)

 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); } 

相当于迅速将是:

 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)") } 

抛出一个错误:

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

将被自动传播给调用者:

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

从苹果的书,Swift编程语言似乎错误应该使用枚举来处理。

这是本书的一个例子。

 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)" } 

来自:苹果公司“Swift编程语言”,iBooks。 https://itun.es/br/jEUH0.l

更新

从苹果新闻书籍“使用cocoa和Objective-C的Swift”。 运行时exception不会发生使用swift语言,所以这就是为什么你没有try-catch。 而是使用可选链 。

这本书是一本书的延伸:

例如,在下面的代码清单中,第一行和第二行不会被执行,因为length属性和characterAtIndex:方法在NSDate对象上不存在。 myLength常量被推断为可选的Int,并被设置为nil。 您也可以使用if-let语句来有条件地解开对象可能不会响应的方法的结果,如第三行所示

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

摘自:苹果公司“使用cocoa和Objective-C的Swift”iBooks。 https://itun.es/br/1u3-0.l


而且这些书也鼓励你使用Objective-C(NSError Object)的cocoa错误模式,

Swift中的错误报告遵循Objective-C中的相同模式,并提供了可选的返回值。 在最简单的情况下,你从函数返回一个Bool值来表示它是否成功。 当您需要报告错误的原因时,您可以向函数添加NSErrorPointertypes的NSError out参数。 这种types大致相当于Objective-C的NSError **,具有额外的内存安全性和可选的打字function。 您可以使用前缀&运算符将可选的NSErrortypes的引用作为NSErrorPointer对象传递,如下面的代码清单所示。

 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)") } } 

摘自:苹果公司“使用cocoa和Objective-C的Swift”iBooks。 https://itun.es/br/1u3-0.l

在Swift中没有例外,类似于Objective-C的方法。

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

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

简单的例子:

 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)") } 

推荐的“快捷方式”是:

 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,因为我发现它更容易遵循,因为它将error handling移动到最后一个单独的块,这种安排有时被称为“黄金path”。 幸运的是,你可以用闭包来做到这一点:

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

也很容易添加一个重试设施:

 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的列表是:

 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) } } 

您可以编写一个类似的类来testing可选的返回值而不是Bool值:

 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版本实施了一个非可选的返回types,使得后续编程更加简单,例如“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 

使用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 } 

注意自动解包。

编辑:虽然这个答案的作品,它只是Objective-C音译成Swift。 它已经被Swift 2.0中的变化所淘汰。 Guilherme Torres Castro上面的回答是一个很好的介绍Swift中处理错误的首选方法。 VOS

我花了一些时间搞清楚,但是我想我已经猜到了。 这似乎很难看。 Objective-C版本只是一个很薄的皮肤。

调用一个NSError参数的函数…

 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 }` 

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

 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 catchfunction。 https://github.com/williamFalcon/SwiftTryCatch

使用像:

 SwiftTryCatch.try({ () -> Void in //try something }, catch: { (error) -> Void in //handle error }, finally: { () -> Void in //close resources }) 

这是swift 2.0的更新答案。 我期待function丰富的error handling模型,如在Java中。 最后,他们宣布了这个好消息。 这里

error handling模型:使用熟悉的try,throw和catch关键字 ,Swift 2.0中的新error handling模型会立即感觉自然。 最重要的是,它被devise为与Apple SDK和NSError完美配合。 事实上,NSError符合Swift的ErrorType。 您一定要观看关于Swift新增function的WWDC会议,以便了解更多信息。

例如:

 func loadData() throws { } func test() { do { try loadData() } catch { print(error) }} 

正如Guilherme托雷斯·卡斯特罗所说,在Swift 2.0中, try ,在编程中可以使用catch

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

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

如果您已经下载了xcode7 Beta。 尝试在文档和API参考中search抛出错误并select第一个显示结果,它给出了一个基本的想法,可以为这个新的语法做些什么。 但是,完整的文档不适用于许多API。

更多花哨的error handling技术可以在这里find

Swift新dynamic(2015 Session 106 28m30s)

error handling是Swift 2.0的一个新特性。 它使用trythrowcatch关键字。

请参阅官方Apple Swift博客上的Apple Swift 2.0声明

正如其他人已经提到的那样,从Swift 2开始,error handling最好通过使用do / try / catch和ErrorType枚举来完成。 这对于同步方法来说效果很好,但是对于asynchronouserror handling需要一点聪明才智。

这篇文章有一个很好的方法来解决这个问题:

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

总结:

 // 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() } 

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

 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 } }) 

这看起来比传递给asynchronous函数的单独的errorHandlercallback要干净一点,Swift 2之前就是这样处理的。

我所看到的是,由于设备的本质,您不希望在用户身上抛出一大堆神秘的error handling消息。 这就是为什么大多数函数返回可选值,然后你只是代码忽略可选。 如果一个函数返回零意味着它失败,你可以popup消息或任何。

好的和简单的库来处理exception: TryCatchFinally-Swift

像其他几个人一样,它围绕着客观的Cexceptionfunction。

像这样使用它:

 try { println(" try") }.catch { e in println(" catch") }.finally { println(" finally") }