Swift语言中的错误处理

时间:2014-06-03 08:35:33

标签: swift error-handling

我没有读过太多关于Swift的内容,但我注意到的一件事是没有例外。 那么他们如何在Swift中进行错误处理呢?有没有人发现任何与错误处理相关的内容?

12 个答案:

答案 0 :(得分:144)

Swift 2& 3

Swift 2中的情况发生了一些变化,因为有一种新的错误处理机制,它与异常更相似但细节不同。

1。指示错误可能性

如果函数/方法想表明它可能会抛出错误,那么它应该包含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& 2之间的主要区别3错误机制和Java / C#/ C ++样式异常如下:

  • 语法略有不同:do-catch + try + defer与传统try-catch-finally语法相比。
  • 异常处理通常会在异常路径中产生比在成功路径中高得多的执行时间。 Swift 2.0错误并非如此,其中成功路径和错误路径的成本大致相同。
  • 必须声明所有错误抛出代码,而异常可能是从任何地方抛出的。所有错误都是Java命名法中的“已检查异常”。但是,与Java相比,您没有指定可能抛出的错误。
  • Swift异常与ObjC异常不兼容。您的do-catch块不会捕获任何NSException,反之亦然,因为您必须使用ObjC。
  • Swift异常与返回NSErrorfalse返回函数)或Boolnil返回函数)的Cocoa AnyObject方法约定兼容并传递NSErrorPointer错误详情。

作为一种额外的合成糖来缓解错误处理,还有两个概念

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

Swift 1

运行时错误:

正如Leandros建议处理运行时错误(如网络连接问题,解析数据,打开文件等),你应该像在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中使用了异常。 Swift语言似乎没有任何语言支持异常(例如throwcatch等关键字)。但是,正如文档所示,它与ObjC在同一运行时运行,因此您仍然可以像这样抛出NSExceptions

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

你无法在纯粹的Swift中捕获它们,尽管你可以选择在ObjC代码中捕获异常。

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

答案 1 :(得分:69)

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

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

  

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。相反,您使用Optional Chaining

以下是本书的内容:

  

例如,在下面的代码清单中,第一行和第二行是   没有执行因为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")
}

摘自:Apple Inc.“将Swift与Cocoa和Objective-C一起使用。”iBooks。 https://itun.es/br/1u3-0.l


书籍还鼓励您使用Objective-C(NSError Object)

中的可可错误模式
  

Swift中的错误报告遵循与其相同的模式   Objective-C,具有提供可选返回的额外好处   值。在最简单的情况下,您从中返回Bool值   用于指示其是否成功的功能。当你需要的时候   报告错误原因,可以添加到功能中   NSError出现NSErrorPointer类型的参数。这种类型粗略   相当于Objective-C的NSError **,具有额外的内存安全性   和可选的打字。你可以使用前缀&amp;运营商传入   引用可选的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

答案 2 :(得分:13)

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

答案 3 :(得分:11)

推荐的'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版本强制执行非可选的返回类型,使后续编程更容易,例如'快速的方式:

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
}

注意自动解包。

答案 4 :(得分:7)

编辑: 虽然这个答案有效,但它只是将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...
}

答案 5 :(得分:4)

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

答案 6 :(得分:3)

这是swift 2.0的更新答案。我期待功能丰富的错误处理模型,如在java中。最后,他们宣布了这个好消息。 here

  

错误处理模型:Swift 2.0中的新错误处理模型将会   通过熟悉的尝试,抛出和捕获关键字,立即感觉自然。   最重要的是,它的设计与Apple SDK和Apple SDK完美配合   NSError。实际上,NSError符合Swift的ErrorType。你会   肯定想观看WWDC上关于Swift的新内容的会议   了解更多信息。

例如:

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

答案 7 :(得分:3)

正如Guilherme Torres Castro所说,在Swift 2.0中,trycatchdo可用于编程。

例如,在CoreData获取数据方法中,而不是将&error作为参数放入managedContext.executeFetchRequest(fetchRequest, error: &error),现在我们只需要使用use managedContext.executeFetchRequest(fetchRequest)然后使用{处理错误{1}},tryApple Document Link

catch

如果您已经下载了xcode7 Beta。尝试在文档和API参考中搜索抛出错误并选择第一个显示结果,它提供了一个基本的想法,可以为这种新语法做些什么。但是,完整的文档还没有发布许多API。

更多精彩的错误处理技术可以在

中找到
  

Swift的新功能(2015年会议106 28m30s)

答案 8 :(得分:2)

错误处理是Swift 2.0的一项新功能。它使用throwcatchset_time_limit()关键字。

请参阅Apple Swift 2.0 announcement on the official Apple Swift blog

答案 9 :(得分:1)

从其他人已经提到的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之前处理它的方式。

答案 10 :(得分:0)

用于处理异常的简单易用的lib: TryCatchFinally-Swift

与其他几个一样,它包含了客观的C异常功能。

像这样使用:

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

答案 11 :(得分:0)

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