Swift HTTP客户端:正确发起网络请求

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程
```html

引言:为什么正确的 HTTP 客户端对您的 Swift 应用很重要

现代 iOS 和 macOS 应用很少能避免与远程服务器交互。无论是从 REST API 加载数据、发送分析数据,还是与云服务协作——Swift 中的 HTTP 客户端都是网络交互的基础。然而,许多开发者,尤其是初学者,会犯典型错误:忽略错误处理、不使用现代语言特性(async/await),或者将网络调用写成“意大利面条式代码”。在本文中,我们将按照最佳实践,讲解如何编写一个可靠且高效的 Swift HTTP 客户端。

1. URLSession 基础:网络请求的核心

在 Swift 标准库中,负责网络交互的是 Foundation 框架和 URLSession 类。这是一个强大的工具,支持会话配置、缓存、后台请求以及更多功能。

1.1. 创建基础 HTTP 客户端

我们从执行 GET 请求的简单结构开始。使用 enum 描述可能的错误,使用 protocol 实现灵活性:

import Foundation

enum HTTPClientError: Error {
    case invalidURL
    case noData
    case decodingError(Error)
    case networkError(Error)
    case statusCode(Int)
}

protocol HTTPClientProtocol {
    func get<T: Decodable>(from urlString: String, responseType: T.Type) async throws -> T
}

final class HTTPClient: HTTPClientProtocol {
    private let session: URLSession
    private let decoder: JSONDecoder
    
    init(session: URLSession = .shared, decoder: JSONDecoder = JSONDecoder()) {
        self.session = session
        self.decoder = decoder
    }
    
    func get<T: Decodable>(from urlString: String, responseType: T.Type) async throws -> T {
        guard let url = URL(string: urlString) else {
            throw HTTPClientError.invalidURL
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        
        let (data, response) = try await session.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse else {
            throw HTTPClientError.networkError(NSError(domain: "Invalid response", code: -1))
        }
        
        guard (200...299).contains(httpResponse.statusCode) else {
            throw HTTPClientError.statusCode(httpResponse.statusCode)
        }
        
        do {
            return try decoder.decode(T.self, from: data)
        } catch {
            throw HTTPClientError.decodingError(error)
        }
    }
}

1.2. 为什么使用 async/await?

在 Swift 5.5 出现之前,开发者使用闭包(completion handlers)或第三方库(Alamofire)。Async/await 使代码线性化、清晰易懂。错误通过 do-catch 处理,而不是通过嵌套闭包。

2. POST、PUT、DELETE:处理不同的 HTTP 方法

真正的 HTTP 客户端必须支持所有主要方法。我们在协议中添加用于发送数据和更新资源的方法。

2.1. 扩展协议以支持 POST 和 PUT

extension HTTPClientProtocol {
    func post<T: Decodable, U: Encodable>(
        to urlString: String,
        body: U,
        responseType: T.Type
    ) async throws -> T {
        guard let url = URL(string: urlString) else {
            throw HTTPClientError.invalidURL
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        request.httpBody = try JSONEncoder().encode(body)
        
        return try a
```                

博客

书籍推荐