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(如throw
, catch
等关键字)。 但是,正如文档所示,它运行在与ObjC相同的运行时,因此您仍然可以像这样抛出NSExceptions
:
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
尽pipe你可以select在ObjC代码中捕获exception,但你不能在纯Swift中捕获它们。
问题在于是否应该为程序员错误抛出exception,或者像苹果在语言指南中所build议的那样使用断言。
2015年6月9日更新 – 非常重要
Swift 2.0自带了try
, throw
和catch
关键字,最让人兴奋的是:
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的一个新特性。 它使用try
, throw
和catch
关键字。
请参阅官方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") }