我想分享一种技术,在使用Swift do, try, catch error处理模型时,我发现它非常有用,可以限制从给定API调用中抛出的错误数量。
目前,Swift不提供类型错误(在其他语言中称为“已检查的异常”,如Java),这意味着抛出的任何函数都可能抛出任何错误。虽然这为我们提供了很大的灵活性,但在使用API时,对于生产代码和测试来说,这也是一个挑战。
考虑以下函数,该函数通过从URL同步加载数据来执行操作:
class AClass { enum SearchError: Error { case invalidParameters(String) } func loadData(_ parameters: String) throws -> Data { let urlString = "https://host/q=\(parameters)" guard let url = URL(string: urlString) else { throw SearchError.invalidParameters(parameters) } return try Data(contentsOf: url)}复制代码
正如您在上面所看到的,我们的函数可以结束在两个不同的位置(尝试构造URL时和使用URL 初始化Data时)。所以,这就是问题所在; 作为一个API用户,我很不清楚我可以期待这个函数抛出什么样的错误。我不需要知道这个函数在Data内部使用哪些类型,但我还需要知道Data初始化程序可以抛出哪些错误。
在API设计中,必须了解imlpementation细节通常是一个不好的迹象,所以如果我们能保证我们的函数只抛出SearchError类型的错误,那不是更好吗?幸运的是,它很容易修复。我们所要做的就是将对数据的调用包装在一个do, try, catch块中。重构以后的代码,像这样:
class AClass { enum SearchError: Error { case invalidParameters(String) case loadingFailed(URL) } func loadData(_ parameters: String) throws -> Data { let urlString = "https://host/q=\(parameters)" guard let url = URL(string: urlString) else { throw SearchError.invalidParameters(parameters) } do { return try Data(contentsOf: url) } catch { throw SearchError.loadingFailed(url) } }}复制代码
我们上面所做的是将初始化Data抛出的异常替换为我们自己的错误。现在,我们可以记录我们的函数总是抛出一个SearchError,并且我们的API在错误处理方面变得更容易使用。
###然而,在使我们的API变得更好的同时,我们的实现也变得混乱了。通常,您需要使用do, try, catch块包装多个调用,这将使我们的代码很快变得难以阅读。为了解决这个问题,我创建了一个简单的函数来执行此包装,并在抛出基础错误时抛出特定错误。它看起来像这样:
func perform(_ expression: @autoclosure () throws -> T, orThrow error: Error) throws -> T { do { return try expression() } catch let error { throw error }}复制代码
###使用它,我们现在可以从新更新我们的loadData功能,使其更简单:
func loadData(_ parameters: String) throws -> Data { let urlString = "https://host/q=\(parameters)" guard let url = URL(string: urlString) else { throw SearchError.invalidParameters(parameters) } return try perform(Data(contentsOf: url), orThrow: SearchError.loadingFailed(url)) }复制代码
我们现在有一个统一的错误API和一个更简单的实现!
这种处理问题的思路,在Alamofire中也有体现,如JSONEncoding中的encode函数
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { var urlRequest = try urlRequest.asURLRequest() guard let parameters = parameters else { return urlRequest } do { let data = try JSONSerialization.data(withJSONObject: parameters, options: options) if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") } urlRequest.httpBody = data } catch { throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) } return urlRequest }复制代码
DataRequest中Requestable也有所体现:
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask { do { let urlRequest = try self.urlRequest.adapt(using: adapter) return queue.sync { session.dataTask(with: urlRequest) } } catch { throw AdaptError(error: error) } }复制代码
如果您有任何问题,建议或反馈,请随时与我联系~