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来重新抛出方法:

avatar

这很好,因为它允许我们只在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方法转发由其函数参数抛出的任何错误。

原文地址