引言:从基础GET到工业级HTTP客户端
Swift中的标准HTTP请求只需三行代码,使用URLSession.shared.dataTask即可。但在实际项目中(文件下载、令牌认证、缓存、重试),这远远不够。本文将深入探讨在iOS和macOS上创建Swift HTTP客户端的高级技术:自定义协议、会话管理、通过async/await和Combine实现的多线程,以及可靠的错误处理。本文面向已熟悉URLSession基础的开发者。
高级HTTP客户端架构
工业级客户端不仅仅是一个请求函数,而是一个抽象层。我们将其分为三个层次:
- 传输层 — 底层URLSession操作,配置管理。
- 服务层 — 序列化/反序列化,状态码处理,重试机制。
- 应用层 — 业务逻辑,缓存,认证。
我们从通过URLProtocol自定义传输层开始。
1. 自定义URLProtocol用于拦截和修改请求
URLProtocol是一种强大的机制,用于拦截应用程序中的所有网络请求。它可以用于添加日志记录、请求签名或测试用的模拟数据。
import Foundation
class LoggingProtocol: URLProtocol { override class func canInit(with request: URLRequest) -> Bool { // 拦截所有HTTP/HTTPS请求 return request.url?.scheme == "http" || request.url?.scheme == "https" }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request }
override func startLoading() { print("📤 请求: \\(request.httpMethod ?? "GET") \\(request.url?.absoluteString ?? "")") print("📦 请求头: \\(request.allHTTPHeaderFields ?? [:])")
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in guard let self = self else { return } if let error = error { print("❌ 错误: \\(error.localizedDescription)") self.client?.urlProtocol(self, didFailWithError: error) } else if let data = data, let response = response { print("✅ 响应: \\(response.expectedContentLength) 字节") self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed) self.client?.urlProtocol(self, didLoad: data) } self.client?.urlProtocolDidFinishLoading(self) } task.resume() }
override func stopLoading() { // 无需操作,任务自行完成 }}
// 注册协议URLProtocol.registerClass(LoggingProtocol.self)let config = URLSessionConfiguration.defaultconfig.protocolClasses = [LoggingProtocol.self]let session = URLSession(configuration: config)现在,通过此会话的所有请求都将被记录。类似地,可以添加自动添加Bearer令牌或处理401状态码的功能。
2. 会话管理:自定义配置与多线程
针对不同目的使用不同的URLSessionConfiguration:.default用于常规请求,.ephemeral用于私有数据(无缓存和Cookie),.background用于后台下载。
let backgroundConfig = URLSessionConfiguration.background(withIdentifier: "com.pythonlib.background")backgroundConfig.isDiscretionary = truebackgroundConfig.sessionSendsLaunchEvents = truelet backgroundSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: nil)对于多线程,使用async/await(Swift 5.5+):
func performRequest(url: URL) async throws -> Data { let (data, response)