这里每天分享一个 iOS 的新知识,快来关注我吧
前言
在上一篇文章中,我们通过封装 URLSession
,创建了一个简单的网络请求工具类。很多小伙伴留言问为什么不用协程,今天就来实现一下。
这一篇文章将继续深入探讨 URLSession
的异步编程,重点介绍两种常见的异步编程方式:Completion Handlers
和 Swift 5.5 引入的 async/await
。
在阅读这篇文章之前,建议你先阅读我们前两篇关于 URLSession
的文章,这样你会更容易理解本文的内容。
URLSession 系列第一篇:iOS 开发者必须掌握的 URLSession 使用指南
URLSession 系列第二篇:手把手教你封装超实用的网络工具类
异步编程简介
在现代应用中,网络请求通常是异步进行的,这样可以避免阻塞主线程,从而保持应用的响应性。
传统上,我们使用 completion handlers
来处理异步操作,但随着 Swift 5.5 的发布,async/await
提供了一种更为简洁和直观的方式来处理异步操作。
使用 Completion Handlers 进行异步编程
在前一篇文章中,我们就是使用 completion handlers
来处理网络请求的结果回调的。
Completion handlers
是一种回调机制,它允许我们在异步操作完成时执行特定的代码。在 URLSession
中,completion handlers
是一个闭包,它在网络请求完成时被调用。
我们之前的 NetworkManager
类中,使用 completion handlers
处理网络请求结果的方式如下:
extension NetworkManager {
func request(_ request: URLRequest, completion: @escaping (Result<Data, NetworkError>) -> Void) {
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(.unknown(error)))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(.unexpectedResponse))
return
}
guard (200...299).contains(httpResponse.statusCode) else {
completion(.failure(.serverError(statusCode: httpResponse.statusCode)))
return
}
if let data = data {
completion(.success(data))
} else {
completion(.failure(.unexpectedResponse))
}
}
task.resume()
}
}
这个方法的逻辑相对比较直观,但是当我们需要发起多个串行的异步请求时,代码就会变得越来越复杂,容易出现回调地狱(Callback Hell)。
因此我们可以很轻易总结出,Completion Handlers
的优点是简单易懂,不受 Swift 版本限制,但是当异步操作嵌套较多时,代码会变得难以维护,错误处理逻辑也可能会分散在多个闭包中。
使用 Async/Await 进行异步编程
Swift 5.5 引入的 async/await
提供了一种更为简洁和易读的方式来处理异步操作。async/await
使得异步代码看起来像同步代码,从而更容易理解和维护。
将 Completion Handlers 转换为 Async/Await
我们需要在 NetworkManager
中把原有的 Completion Handlers
的方法改成使用 async/await 的方法。
extension NetworkManager {
func requestAsync(_ request: URLRequest) async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
self.request(request) { result in
switch result {
case .success(let data):
continuation.resume(returning: data)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
func getJSONAsync(url: URL) async throws -> [String: Any] {
let data: Data = try await requestAsync(URLRequest(url: url))
return try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
}
func postAsync(url: URL, body: Data?) async throws -> Data {
var request: URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = body
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
return try await requestAsync(request)
}
}
requestAsync
方法使用 withCheckedThrowingContinuation
将使用 completion handlers 的 request
方法转换为 async/await 风格。getJSONAsync
方法则是调用 requestAsync
来获取数据并解析为 JSON。
这样改完之后就可以使用 async/await
的方式来处理网络请求了。
使用 Async/Await 的示例
现在,我们来看一下如何使用这些 async/await
方法。
GET 请求:
func getJSON() async {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
do {
let json = try await NetworkManager.shared.getJSONAsync(url: url)
print("JSON: \(json)")
} catch {
print("Error: \(error)")
}
}
POST 请求:
func post() async {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let body = """
{
"title": "标题",
"body": "这是一个新的帖子",
"userId": 1
}
""".data(using: .utf8)
do {
let data = try await NetworkManager.shared.postAsync(url: url, body: body)
if let responseString = String(data: data, encoding: .utf8) {
print("Response: \(responseString)")
}
} catch {
print("Error: \(error)")
}
}
调用这两个方法:
await getJSON()
await post()
可以看到,使用 async/await 后,代码更加简洁和直观,不再需要嵌套多个回调。
总结
在这篇文章中,我们探讨了两种常见的异步编程方式:Completion Handlers
和 async/await
。通过对比可以看到,async/await
使得异步代码更为简洁和易读,大大简化了异步操作的处理。
希望通过这篇文章,你能够更好地理解和使用这两种异步编程方式,在实际项目中根据需求选择合适的方式进行异步操作。
下一篇文章将继续探讨 URLSession
的其他高级用法,敬请期待!
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!