URLSession 系列第三篇:告别回调地狱,掌握 Completion Handlers 和 async/await!

1,460 阅读4分钟

这里每天分享一个 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 Handlersasync/await。通过对比可以看到,async/await 使得异步代码更为简洁和易读,大大简化了异步操作的处理。

希望通过这篇文章,你能够更好地理解和使用这两种异步编程方式,在实际项目中根据需求选择合适的方式进行异步操作。

下一篇文章将继续探讨 URLSession 的其他高级用法,敬请期待!

这里每天分享一个 iOS 的新知识,快来关注我吧

本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!