@escaping 被称为逃逸闭包。该闭包会在函数执行完成之后再调用。在学习iOS开发的实践中,我发现 @escaping 常常被用来处理异步回调的情况。在这篇文章中,我将会分享一些我在实践中遇到的 @escaping 的用法。
用逃逸闭包将函数内部的参数传递出去
现在假设我们已经有了一个 AuthManager 类用来管理我们 App 的 API 验证授权 (OAuth2.0)。
我们在 APICaller.swift 中需要经常创建 URLRequest 去调用 API,因此将这段代码抽出来复用。
private func createRequest(
with url: URL?,
type: HTTPMethod,
completion: @escaping (URLRequest) -> Void
) {
AuthManager.shared.withValidToken { token in
guard let apiURL = url else {
return
}
var request = URLRequest(url: apiURL)
request.setValue("Bearer \(token)",
forHTTPHeaderField: "Authorization")
request.httpMethod = type.rawValue
request.timeoutInterval = 30
completion(request)
}
}在这里我们可以看到该方法会调用 AuthManager 中的 withValidToken 方法,该方法的作用是使得 token 永远保持有效状态。如果无效则会刷新。其实在withValidToken 也使用了逃逸闭包,这里暂且按下不表。
通过 completion(request) 我们将 URLRequest 传递给了调用者。这样调用者就可以使用 URLRequest 去调用 API 了。
我们看看如何调用。
用逃逸闭包返回网络请求的结果
public func getFeaturedPlaylists(completion: @escaping ((Result<FeaturedPlaylistsResponse, Error>) -> Void)) {
let url = URL(string: Constants.baseAPIURL + "/browse/featured-playlists?limit=20")
performRequest(url: url, type: .GET, completion: completion)
}
private func performRequest<T: Decodable>(url: URL?, type: HTTPMethod, completion: @escaping (Result<T, Error>) -> Void) {
createRequest(with: url, type: type) { request in
let task = URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data, error == nil else {
completion(.failure(APIError.failedToGetData))
return
}
do {
let result = try JSONDecoder().decode(T.self, from: data)
completion(.success(result))
}
catch {
completion(.failure(error))
}
}
task.resume()
}
performRequest 中的代码同样是需要备多次重复的。这个方法接受的最后一个参数同样是一个闭包。该闭包的作用是将 Result 传递给调用者。不过此时我们先来看看其他部分。该方法调用了createRequest方法,并传入了一个闭包。
在方法内部,它首先调用了 createRequest 方法来创建一个 URLRequest 对象。然后,它使用 URLSession.shared.dataTask(with:completionHandler:) 方法发起异步网络请求,并在请求完成后处理响应。当请求成功返回数据时,使用 JSONDecoder 对返回的数据进行解码,并将解码后的结果通过 completion(.success(result)) 返回给调用者。如果请求失败或解码过程中发生错误,则通过 completion(.failure(error)) 返回错误信息。
通过使用泛型 <T: Decodable>,可以灵活地传入不同类型的模型对象来进行网络请求,并通过 Result 类型的闭包参数返回请求结果。
处理请求结果(成功还是失败)
最后 getFeaturedPlaylists 方法调用了 performRequest 方法,并传入了一个闭包。我们在真正调用 getFeaturedPlaylists 方法的时候,代码如下:
group.enter()
APICaller.shared.getFeaturedPlaylists { result in
handleResult(group, result: result, modelStorage: &featuredPlaylist)
}
func handleResult<ModelType>(_ group: DispatchGroup, result: Result<ModelType, Error>, modelStorage: inout ModelType?) -> Void {
defer {
group.leave()
}
switch result {
case .success(let model):
modelStorage = model
case .failure(let error):
print(error.localizedDescription)
}
}在 DispatchGroup中创建任务。而我们为getFeaturedPlaylists 传入的 completion 其实就是一个 switch case 语句。当请求成功时,我们将 modelStorage 赋值为 model。当请求失败时,我们打印出错误信息。