1. How to use the rethrows keyword in Swift
Swift中的rethrows允许通过给定的函数参数转发抛出的错误。它在map、filter和forEach等方法中大量使用,并帮助编译器确定是否需要try前缀。
根据我的经验,您不必经常编写rethrows方法。但是,一旦您了解了它是如何工作的,您就会开始看到更多rethrows方法有意义的情况。
1.1. How to use the rethrows keyword
rethrows关键字用于函数本身不抛出错误,而是通过其函数参数转发错误的函数中。它还允许编译器仅在给定的回调实际上抛出错误时才需要try关键字。
下面是一个带有throwing回调的rethrowing方法的例子:
func rethrowingFunction(throwingCallback: () throws -> Void) rethrows {
try throwingCallback()
}
如果我们传入的回调函数没有抛出错误,我们可以如下方式调用该方法:
rethrowingFunction {
print("I'm not throwing errors")
}
然而,一旦回调可能抛出错误,编译器就要求我们使用try来重新抛出方法:

这很好,因为它允许我们只在body真正抛出错误时使用try关键字。如果不可能接收到错误,就没有必要将我们的方法封装在try-catch中。
如果我们要编写相同的方法而不重新抛出,我们最终将不得不在所有情况下使用try:
func rethrowingFunction(throwingCallback: () throws -> Void) throws {
try throwingCallback()
}
try rethrowingFunction {
print("I'm not throwing errors")
}
换句话说,只有当重新抛出方法的函数参数可能抛出错误时,才需要将其标记为try。
1.2. A real case example
现在我们知道了rethrow关键字是如何工作的,接下来来看一个实际的例子。在下面的代码中,我们为字符串数组创建了一个包装器方法,在这个方法中,我们基于predicate连接元素:
extension Array where Self.Element == String {
func joined(separator: String, where predicate: (Element) throws -> Bool) rethrows {
try filter(predicate)
.joined(separator: separator)
}
}
默认情况下,标准filter方法是rethrowing错误。但是,如果我们想从包装连接方法中的这种行为中获益,我们也需要使自定义方法rethrowing。否则,就不能在predicate中抛出任何错误。
用法示例如下:
enum Error: Swift.Error {
case numbersNotAllowed
}
var names = ["Antoine", "Maaike", "Bernie", "Angi3"]
do {
try names.joined(separator: ", ", where: { name -> Bool in
guard name.rangeOfCharacter(from: .decimalDigits) == nil else {
throw Error.numbersNotAllowed
}
return true
})
} catch {
print(error) // Prints: `numbersNotAllowed`
}
由于名称中包含数字3,因此join方法将抛出一个错误。
1.3. Using rethrows to wrap errors
另一个常见的用例是将其他错误封装到本地定义的错误类型中。在下面的例子中,我们定义了一个存储控制器,它返回一个带有强类型StorageError的Result enum。但是,我们的FileManager方法可以抛出任何我们想要在StorageError.otherError中封装的其他错误情况。
在给定的回调中使用rethrowing perform方法,我们可以捕获任何发生的错误:
struct StorageController {
enum StorageError: Swift.Error {
case fileDoesNotExist
case otherError(error: Swift.Error)
}
let destinationURL: URL
func store(_ url: URL, completion: (Result<URL, StorageError>) -> Void) throws {
guard FileManager.default.fileExists(atPath: url.path) else {
completion(.failure(StorageError.fileDoesNotExist))
return
}
try perform {
try FileManager.default.moveItem(at: url, to: destinationURL)
completion(.success(destinationURL))
}
}
private func perform(_ callback: () throws -> Void) rethrows {
do {
try callback()
} catch {
throw StorageError.otherError(error: error)
}
}
}
1.4. Overriding methods with the rethrows keyword
重要的是要理解,throwing方法不能用于覆盖rethrowing方法,因为它会突然将“可能抛出”方法变成“抛出”方法。出于同样的原因,throwing方法也不能满足rethrowing方法的协议要求。
另一方面,一个rethrowing方法可以覆盖一个throwing方法,就像在最后,一个抛出方法也是一个'可能抛出函数。这也意味着您可以使用rethrowing方法来满足throwing方法的协议要求。
1.5. Conclusion
在Swift中rethrowing可以很好地防止无缘无故使用try关键字。如果内部方法没有抛出错误,则rethrows关键字确保编译器知道不需要尝试。rethrowing方法转发由其函数参数抛出的任何错误。