首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Swift语言中的错误处理

Swift语言中的错误处理
EN

Stack Overflow用户
提问于 2014-06-03 08:35:33
回答 12查看 94K关注 0票数 194

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

EN

回答 12

Stack Overflow用户

回答已采纳

发布于 2014-06-04 05:56:37

Swift 2&3

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

1.指示错误的可能性

如果函数/方法想表明它可能抛出错误,那么它应该包含像下面这样的throws关键字

代码语言:javascript
复制
func summonDefaultDragon() throws -> Dragon

注意:函数实际上可以抛出的错误类型没有规范。这个声明只是声明函数可以抛出实现ErrorType的任何类型的实例,或者根本不抛出。

2.调用可能引发错误的函数

为了调用函数,需要使用try关键字,如下所示

代码语言:javascript
复制
try summonDefaultDragon()

这一行通常应该在下面这样的do-catch块中。

代码语言:javascript
复制
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关键字的函数调用抛出函数,则可以决定传播错误:

代码语言:javascript
复制
func fulfill(quest: Quest) throws {
    let dragon = try summonDefaultDragon()
    quest.ride(dragon)
} 

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

代码语言:javascript
复制
let dragonOrNil = try? summonDefaultDragon()

这样,您可以获得返回值或零(如果发生任何错误)。使用这种方式,您将不会获得error对象。

这意味着您还可以将try?与有用的语句组合起来,例如:

代码语言:javascript
复制
if let dragon = try? summonDefaultDragon()

代码语言:javascript
复制
guard let dragon = try? summonDefaultDragon() else { ... }

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

代码语言:javascript
复制
let dragon = try! summonDefaultDragon()

如果函数实际上抛出了一个错误,那么您将在应用程序中得到一个运行时错误,并且应用程序将终止。

3.抛出错误

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

代码语言:javascript
复制
throw DragonError.dragonIsMissing

您可以抛出符合ErrorType协议的任何内容。首先,NSError符合此协议,但您可能希望使用基于枚举的ErrorType,它使您能够对多个相关错误进行分组,可能还会使用其他数据块,如下所示

代码语言:javascript
复制
enum DragonError: ErrorType {
    case dragonIsMissing
    case notEnoughMana(requiredMana: Int)
    ...
}

新的Swift 2&3错误机制与Java/C#/C++类型异常的主要区别如下:

  • 语法有点不同:do-catch + try + defer与传统的try-catch-finally语法不同。
  • 异常处理通常在异常路径中比在成功路径中需要更长的执行时间。SWIFT2.0错误的情况并非如此,成功路径和错误路径的成本大致相同。
  • 必须声明抛出代码的所有错误,而异常可能是从任何地方抛出的。所有错误都是Java术语中的“检查异常”。但是,与Java不同的是,您没有指定可能引发的错误。
  • Swift异常与ObjC异常不兼容。您的do-catch块不会捕获任何NSException,反之亦然,因为您必须使用ObjC。
  • Swift异常与Cocoa NSError方法惯例兼容,即返回false (用于Bool返回函数)或nil (用于AnyObject返回函数),并传递包含错误详细信息的NSErrorPointer

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

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

Swift 1

运行时错误:

正如Leandros在处理运行时错误(如网络连接问题、解析数据、打开文件等)所建议的那样,您应该像在ObjC中那样使用ObjC,因为基金会、AppKit、UIKit等都以这种方式报告它们的错误。所以与其说是语言,不如说是框架。

另一个常用的模式是分隔成功/失败块,如在AFNetworking中:

代码语言:javascript
复制
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

代码语言:javascript
复制
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()

您只是无法在纯Swift中捕获它们,尽管您可能选择在ObjC代码中捕获异常。

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

票数 152
EN

Stack Overflow用户

发布于 2014-06-03 08:44:33

Swift中没有任何例外,类似于Objective的方法.

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

传统的NSError方法不会改变,您可以发送一个NSErrorPointer,它会被填充。

简短的例子:

代码语言:javascript
复制
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)")
}
票数 13
EN

Stack Overflow用户

发布于 2014-11-05 04:06:39

推荐的“快速方式”是:

代码语言:javascript
复制
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
}

然而,我更喜欢尝试/捕捉,因为我发现它更容易跟踪,因为它将错误处理移动到另一个块的末尾,这种安排有时被称为“黄金路径”。幸运的是,您可以使用闭包来完成这一任务:

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

此外,添加重试工具也很容易:

代码语言:javascript
复制
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的清单是:

代码语言:javascript
复制
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值:

代码语言:javascript
复制
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版本强制执行一种非可选的返回类型,使后续编程更容易。“快速的方式:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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
}

注意自动展开。

票数 12
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24010569

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档