我没有读过太多关于Swift的信息,但我注意到的一件事是没有例外.那么他们如何在Swift中进行错误处理呢?有没有人发现任何与错误处理相关的内容?
Swift 2中的情况发生了一些变化,因为有一种新的错误处理机制,它与异常更相似但细节不同.
如果函数/方法想要指示它可能抛出错误,它应该包含这样的throws
关键字
func summonDefaultDragon() throws -> Dragon
注意:函数实际上可以抛出的错误类型没有规范.该声明只是声明该函数可以抛出任何实现ErrorType的类型的实例,或者根本不抛出.
为了调用函数,你需要使用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模式匹配的所有强大功能,因此您在这里非常灵活.
如果您正在使用自己标记为throws
关键字的函数调用throw函数,则可能决定传播错误:
func fulfill(quest: Quest) throws { let dragon = try summonDefaultDragon() quest.ride(dragon) }
或者,您可以使用try?
以下方法调用throw函数:
let dragonOrNil = try? summonDefaultDragon()
这样,如果发生任何错误,您将获得返回值或nil.使用这种方式,您不会得到错误对象.
这意味着您还可以结合try?
有用的语句,例如:
if let dragon = try? summonDefaultDragon()
要么
guard let dragon = try? summonDefaultDragon() else { ... }
最后,您可以决定是否知道实际上不会发生错误(例如,因为您已经检查过先决条件)并使用try!
关键字:
let dragon = try! summonDefaultDragon()
如果函数实际抛出错误,那么您将在应用程序中收到运行时错误,应用程序将终止.
为了抛出错误,你可以像这样使用throw关键字
throw DragonError.dragonIsMissing
你可以扔任何符合ErrorType
协议的东西.对于初学者来说NSError
,这符合这个协议,但你可能希望使用基于enum的方法ErrorType
,这样你就可以将多个相关的错误分组,可能还有额外的数据,比如这个
enum DragonError: ErrorType { case dragonIsMissing case notEnoughMana(requiredMana: Int) ... }
新的Swift 2和3错误机制与Java/C#/ C++样式异常之间的主要区别如下:
语法有点不同:do-catch
+ try
+ defer
vs传统try-catch-finally
语法.
异常处理通常会在异常路径中产生比在成功路径中高得多的执行时间.Swift 2.0错误的情况并非如此,其中成功路径和错误路径的成本大致相同.
必须声明所有错误抛出代码,而异常可能从任何地方抛出.所有错误都是Java命名法中的"已检查异常".但是,与Java相比,您不指定可能抛出的错误.
Swift异常与ObjC异常不兼容.您的do-catch
块不会捕获任何NSException,反之亦然,因为您必须使用ObjC.
Swift异常与NSError
返回false
(Bool
返回函数)或nil
(AnyObject
返回函数)和传递NSErrorPointer
错误详细信息的Cocoa 方法约定兼容.
作为一种额外的合成糖来缓解错误处理,还有两个概念
延迟动作(使用defer
关键字),它可以实现与Java/C#/ etc中的finally块相同的效果
保护语句(使用guard
关键字),它可以让你写的if/else代码少于正常的错误检查/信令代码.
运行时错误:
正如Leandros建议处理运行时错误(如网络连接问题,解析数据,打开文件等),你应该NSError
像在ObjC中那样使用,因为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中使用了异常.雨燕语言似乎并未有任何异常的语言支持(如throw
,catch
等关键字).但是,正如文档所示,它与ObjC在同一运行时运行,因此您仍然可以NSExceptions
像这样抛出:
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
虽然您可以选择在ObjC代码中捕获异常,但您无法在纯Swift中捕获它们.
问题是你是否应该抛出程序员错误的异常,或者更确切地说使用Apple在语言指南中建议的断言.
2015年6月9日更新 - 非常重要
雨燕2.0配备try
,throw
以及catch
关键字和最令人兴奋的是:
Swift会自动将产生错误的Objective-C方法转换为根据Swift的本机错误处理功能引发错误的方法.
注意:消耗错误的方法(如委托方法或采用NSError对象参数的完成处理程序的方法)不会成为Swift导入时抛出的方法.
摘录自:Apple Inc."将Swift与Cocoa和Objective-C一起使用(Swift 2 Prerelease)."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); }
swift中的等价物将是:
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)
从Apple书籍,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)" }
来自:Apple Inc."The Swift Programming Language."iBooks.https://itun.es/br/jEUH0.l
更新
来自Apple新闻书,"使用Swift与Cocoa和Objective-C".使用swift语言不会发生运行时异常,这就是为什么你没有try-catch.而是使用可选链接.
这是书中的一段:
例如,在下面的代码清单中,不执行第一行和第二行,因为在NSDate对象上不存在length属性和characterAtIndex:方法.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") }
摘录自:Apple Inc."将Swift与Cocoa和Objective-C结合使用."iBooks.https://itun.es/br/1u3-0.l
书籍还鼓励你使用Objective-C中的可可错误模式(NSError Object)
Swift中的错误报告遵循与Objective-C中相同的模式,并提供了提供可选返回值的额外好处.在最简单的情况下,您从函数返回Bool值以指示它是否成功.当您需要报告错误原因时,可以向函数添加NSErrorPointer类型的NSError out参数.这种类型大致相当于Objective-C的NSError**,具有额外的内存安全性和可选的输入.您可以使用前缀&运算符将对可选NSError类型的引用作为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)") } }
摘录自:Apple Inc."将Swift与Cocoa和Objective-C结合使用."iBooks.https://itun.es/br/1u3-0.l
推荐的'Swift Way'是:
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,因为我发现它更容易理解,因为它将错误处理移动到最后一个单独的块,这种安排有时被称为"黄金路径".幸运的是你可以用闭包来做到这一点:
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) } }
您可以编写一个类似的类来测试Optional返回值而不是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版本强制执行非可选返回类型,使后续编程更容易,例如'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... }
Swift中没有Exceptions,类似于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)") }
目标C周围的基本包装器为您提供了try catch功能。 https://github.com/williamFalcon/SwiftTryCatch
使用方式如下:
SwiftTryCatch.try({ () -> Void in //try something }, catch: { (error) -> Void in //handle error }, finally: { () -> Void in //close resources })