在以下示例中,函数usingTemporaryDirectory()
创建并删除临时目录,在其间调用传递的函数body()
。如果createTemporaryDirectory()
或传递的函数body()
抛出异常,它将传播给调用者。但是removeDirectory()
引发的异常无法传递给调用者,因为没有异常可能会转义defer
块。
import Foundation
func createTemporaryDirectory() throws -> URL { ... }
func removeDirectory(_ url: URL) throws { ... }
func usingTemporaryDirectory(body: (URL) throws -> ()) throws {
let tempDir = try createTemporaryDirectory()
defer {
// Errors thrown from here are not handled.
try removeDirectory(tempDir)
}
try body(tempDir)
}
处理此类异常的正确方法是什么?我看到两个选择:
我不想使用选项1,因为这可能导致此示例中的任意数量的临时目录堆积起来。而且我也不想使用选项2,因为这会阻止外部堆栈帧完成清理工作(例如,如果已创建多个临时目录,则至少应尝试删除所有这些目录)。
Java有一个名为suppressed exceptions的功能。在这种情况下,defer
块中抛出的异常可以作为抑制异常添加到body()
引发的异常(如果有)。 Swift有相似的功能吗?
答案 0 :(得分:2)
在这种情况下,延迟块中抛出的异常可以作为
body()
引发的异常的抑制异常添加,如果有的话。 Swift有相似的功能吗?
不是我知道的 - 但你可以自己建造类似的东西。首先,让我们定义一个Error
类型,它可以存储多个底层错误:
struct ErrorCollection : Error {
private var errors: [Error] = []
init() {}
init<S : Sequence>(_ sequence: S) where S.Element == Error {
for error in sequence {
append(error)
}
}
mutating func append(_ error: Error) {
switch error {
case let x as ErrorCollection: // ensure we flatten out any nested error collections.
errors.append(contentsOf: x.errors)
case let x:
errors.append(x)
}
}
}
extension ErrorCollection : RandomAccessCollection {
typealias Index = Int
typealias Element = Error
var startIndex: Index { return errors.startIndex }
var endIndex: Index { return errors.endIndex }
func index(_ i: Index, offsetBy n: Index) -> Index {
return errors.index(i, offsetBy: n)
}
subscript(index: Index) -> Element { return errors[index] }
}
然后我们可以定义一个Result<T>
类型,我们可以使用它来评估一个闭包,如果成功则存储返回值,否则它会抛出错误。然后,我们可以添加then(_:)
方法,以便我们捕获任何其他错误,从而可能导致成功值失效:
enum Result<T> {
case success(T)
case failure(Error)
init(_ body: () throws -> T) {
do {
self = .success(try body())
} catch {
self = .failure(error)
}
}
func then(_ body: () throws -> Void) -> Result {
do {
try body()
return self
} catch let nextError {
switch self {
case .success: // invalidate the success value and store the error.
return .failure(nextError)
case .failure(let error): // concatenate the errors.
return .failure(ErrorCollection([error, nextError]))
}
}
}
func materialize() throws -> T {
switch self {
case .success(let value):
return value
case .failure(let error):
throw error
}
}
}
然后您可以这样使用:
func usingTemporaryDirectory<R>(body: (URL) throws -> R) throws -> R {
let tempDir = try createTemporaryDirectory()
return try Result { try body(tempDir) }
.then { try removeDirectory(tempDir) }
.materialize()
}