快速语言中的错误处理

我对斯威夫特没有太多的了解,但是我注意到没有例外。 那么他们是如何在 Swift 中进行错误处理的呢? 有人发现任何与错误处理相关的东西吗
94064 次浏览

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

Swift 2 & 3

在 Swift 2中,情况发生了一些变化,因为出现了一种新的错误处理机制,这种机制与异常有些类似,但在细节上有所不同。

1. 指示错误的可能性

如果 function/method 希望指示它可能抛出一个错误,它应该包含如下 throws关键字

func summonDefaultDragon() throws -> Dragon

注意: 没有关于函数实际可能抛出的错误类型的规范。这个声明只是说明函数可以抛出实现 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 模式匹配的所有强大功能,所以你在这里非常灵活。

如果您正在从一个本身标有 throws关键字的函数调用一个抛出函数,那么您可能会决定传播这个错误:

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

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

let dragonOrNil = try? summonDefaultDragon()

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

这意味着您还可以将 try?与以下有用的语句组合在一起:

if let dragon = try? summonDefaultDragon()

或者

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

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

let dragon = try! summonDefaultDragon()

如果函数真的抛出了一个错误,那么在应用程序中将得到一个运行时错误,应用程序将终止。

3. 抛出错误

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

throw DragonError.dragonIsMissing

您可以抛出任何符合 ErrorType协议的内容。对于初学者来说,NSError符合这个协议,但是您可能希望使用基于枚举的 ErrorType,它允许您对多个相关错误进行分组,可能还包含其他数据片段,如下所示

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 不同,您不需要指定可能抛出的错误。
  • 快速异常与对象异常不兼容。do-catch块不会捕获任何 NSException,反之亦然,因此必须使用 OBC。
  • 快速异常与 Cocoa NSError方法约定兼容,即返回 false(对于 Bool返回函数)或 nil(对于 AnyObject返回函数)并传递带有错误详细信息的 NSErrorPointer

作为一个用于简化错误处理的额外语法糖,还有两个概念

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

Swift 1

运行时错误:

Leandros 建议用于处理运行时错误(比如网络连接问题、解析数据、打开文件等等) ,你应该像在 ObjecC 中那样使用 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实例。

程序员错误:

对于程序员错误(比如数组元素的出界访问、传递给函数调用的无效参数等等) ,您使用的是 OBC 中的异常。Swift 语言似乎不支持任何异常(如 throwcatch等关键字)。但是,正如文档所表明的那样,它运行在与 ObjecC 相同的运行时上,因此您仍然可以像下面这样抛出 NSExceptions:

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

您只是不能在纯 Swift 中捕获它们,尽管您可以选择在 OBC 代码中捕获异常。

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

< em > 编辑:虽然这个答案有效,但它只不过是 Objective-C 转译成 Swift 而已。由于 Swift 2.0的改变,它已经过时了。吉列尔梅 · 托雷斯 · 卡斯特罗上面的回答是对 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...
}

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

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

根据 Swift 的本机错误处理功能,Swift 会自动将产生错误的 Objective-C 方法转换为抛出错误的方法。

注意: 使用错误的方法,如委托方法或方法 使用带有 NSERror 对象参数的完成处理程序,则不要 成为 Swift 导入时抛出的方法

摘自: 苹果公司“使用 Swift 与可可和 Objective-C (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)

从苹果的书籍,迅速编程语言,它的错误似乎应该使用枚举处理。

这里有一个书中的例子。

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

来自: 苹果公司“快速编程语言”iBooks https://itun.es/br/jeuh0.l

更新

摘自苹果新闻书籍《用 Swift 搭配可可和 Objective-C 》。使用快速语言不会发生运行时异常,这就是为什么没有 try-catch 的原因。而是使用 可选链接

以下是书中的一段延伸:

例如,在下面的代码清单中,第一行和第二行是 因为 length 属性和 trait AtIndex: 方法在 NSDate 对象上不存在 推断为可选的 Int,并设置为 nil 语句有条件地取消包装的方法的结果 对象可能不会响应,如第三行

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

节选自: 苹果公司“用可可和 Objective-C 搭配使用 Swift”iBooks https://itun.es/br/1u3-0.l


这些书还鼓励你使用 Objective-C (NSERror Object)中的可可错误模式

Swift 中的错误报告遵循相同的模式 Objective-C,提供可选回报的额外好处 在最简单的情况下,从 功能,以表明它是否成功。当你需要 报告错误的原因时,可以向函数中添加 类型为 NSERrorPointer 的 NSERrorout 参数 相当于 Objective-C 的 NSERror * * ,具有额外的内存安全性 和可选类型。您可以使用前缀 & 运算符传入 作为 NSERrorPointer 对象引用可选的 NSERror 类型,如下所示 如下面的代码清单所示
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)")
}
}

节选自: 苹果公司“用可可和 Objective-C 搭配使用 Swift”iBooks https://itun.es/br/1u3-0.l

围绕目标 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
})

推荐的“快捷方式”是:

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

您可以编写一个类似的类来测试可选返回值而不是 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)
}
}

TryOptions 版本强制执行一个非可选的返回类型,这使得后续编程更加容易,例如‘ 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

使用 TryOptions:

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
}

注意自动拆封。

漂亮而简单的 lib 来处理异常: TryCatchfinally-Swift

与其他一些特性一样,它包含了客观的 C 异常特性。

像这样使用它:

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

这是迅捷2.0的更新答案。我期待特性丰富的错误处理模型,如在 Java。最后,他们宣布了这个好消息。给你

错误处理模型: Swift 2.0中新的错误处理模型将 立即感觉自然,熟悉 尝试、抛出和捕捉关键字。 最棒的是,它被设计成与苹果 SDK 和 事实上,NSERror 符合 Swift 的 ErrorType 我非常想看看 WWDC 的节目“ What’s New in Swift to” 再多听听

例如:

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

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

错误处理是 Swift 2.0的一个新特性,它使用了 trythrowcatch关键字。

看看 Apple Swift 官方博客上发布了 Apple Swift 2.0的声明

正如吉列尔梅 · 托雷斯 · 卡斯特罗所说,在 Swift 2.0、 trycatchdo中都可以用到编程。

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

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

如果您已经下载了 xcode7Beta。尝试在 文件和 API 参考中搜索 投掷失误并选择第一个显示的结果,它给出了一个基本的想法,这个新的语法可以做什么。然而,许多 API 的完整文档还没有发布。

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

斯威夫特最新消息(2015年第106期28m30s)

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

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

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

这似乎比将单独的 errorHandler 回调传递给异步函数(在 Swift 2之前就是这样处理的)要简单一些。

enum CheckValidAge : Error{
case overrage
case underage
}


func checkValidAgeForGovernmentJob(age:Int)throws -> Bool{
if age < 18{
throw CheckValidAge.underage
}else  if age > 25{
throw  CheckValidAge.overrage
}else{
return true
}
}


do {
try checkValidAgeForGovernmentJob(age: 26)
print("You are valid for government job ")
}catch CheckValidAge.underage{
print("You are underage for government job ")
}catch CheckValidAge.overrage{
print("You are overrage for government job ")
}

在 try 中更改年龄 checkValidAgeForGovernment Job (年龄: 26)

< p > 输出 你太热衷于政府工作了