由于工作需要,新需求已经切到Swift。
我想分享一种技术,在使用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<T>(_ 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)
}
}
如果您有任何问题,建议或反馈,请随时与我联系~
网友评论