如何针对响应错误实现GET请求的智能响应缓存
问题
我希望仅在特定响应错误场景下使用缓存:仅当GET请求出现无网络或请求超时错误时,才从缓存获取数据;请求成功时则更新缓存。我已编写了一个可用的示例代码,但认为该逻辑可以更严谨、简洁。请问需要注意哪些要点,或者可以从哪里查阅这类方案的相关资料?
优化要点与注意事项
- 缓存有效性校验:当前代码仅存储缓存时间,但实际场景中必须加入缓存过期判断,避免返回过期过久的旧数据。存储缓存时需记录过期时间,读取前先校验有效性。
- 错误场景精准匹配:当前仅处理
.sessionTaskFailed下的URLError,需明确:只有无网络、超时这两类错误才触发缓存读取,其他错误(如服务器5xx、4xx)直接返回原错误,避免逻辑混淆。 - 请求方法严格限制:必须确保只有
GET请求走缓存逻辑,POST/PUT等写请求绝不触发缓存,防止数据一致性问题。 - 成功响应过滤:更新缓存时仅保留2xx状态码的成功响应,避免把404、500这类错误响应存入缓存。
- 线程安全:
URLCache本身是线程安全的,但如果后续扩展自定义缓存逻辑,必须处理多线程读写同步,避免竞态条件。 - 资源占用控制:当前设置的内存缓存10MB、磁盘缓存1GB,需根据业务实际调整,避免占用过多设备存储空间。
精简优化后的示例代码
import Alamofire import SwiftUI struct ContentView: View { @StateObject private var viewModel: ViewModel = .init() var body: some View { VStack { Button("Reload", action: viewModel.onLoad) ScrollView { Text(viewModel.result) .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) } } } } extension ContentView { class ViewModel: ObservableObject { typealias ResponseInfo = (HTTPURLResponse?, Result<Data?, AFError>) @Published private(set) var result: String = "" private let session: Alamofire.Session private let cache: URLCache private let cacheExpirationInterval: TimeInterval = 3600 // 缓存1小时过期 init() { self.session = .init( configuration: .default, requestQueue: .init(label: "Alamofire.Session.Queue") ) self.cache = { let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] .appendingPathComponent("APICache") return URLCache(memoryCapacity: 10_000_000, diskCapacity: 1_000_000_000, directory: cacheDir) }() } func onLoad() { result = "" runRequest(url: "https://google.com") { [weak self] response, result in guard let self = self else { return } self.result = self.formatResponseInfo(response: response, result: result) } } private func runRequest(url: String, handleResponse: @escaping (ResponseInfo) -> Void) { session.request(url).response { [weak self] responseData in guard let self = self else { return } DispatchQueue.main.async { let request = responseData.request let result = responseData.result // 仅处理GET请求的缓存逻辑 guard let getRequest = request, getRequest.method == .get else { return handleResponse((responseData.response, result)) } switch result { case .success(let data): // 仅当响应成功(2xx状态码)时更新缓存 if let httpResponse = responseData.response, 200...299 ~= httpResponse.statusCode, let data = data { let cachedResponse = CachedURLResponse( response: httpResponse, data: data, userInfo: ["cachedAt": Date()], storagePolicy: .allowed ) self.cache.storeCachedResponse(cachedResponse, for: getRequest) } handleResponse((responseData.response, result)) case .failure(let error): // 仅在无网络或超时错误时尝试读取有效缓存 if self.shouldUseCache(for: error), let cachedResponse = self.cache.cachedResponse(for: getRequest), self.isCacheValid(cachedResponse: cachedResponse) { return handleResponse((cachedResponse.response as? HTTPURLResponse, .success(cachedResponse.data))) } handleResponse((responseData.response, result)) } } } } // 判断是否触发缓存读取的错误类型 private func shouldUseCache(for error: AFError) -> Bool { guard case .sessionTaskFailed(let underlyingError) = error, let urlError = underlyingError as? URLError else { return false } return [.notConnectedToInternet, .timedOut].contains(urlError.code) } // 校验缓存是否过期 private func isCacheValid(cachedResponse: CachedURLResponse) -> Bool { guard let cachedAt = cachedResponse.userInfo?["cachedAt"] as? Date else { return false } return Date().timeIntervalSince(cachedAt) <= cacheExpirationInterval } // 格式化响应结果为展示文本 private func formatResponseInfo(response: HTTPURLResponse?, result: Result<Data?, AFError>) -> String { var text = "" text += "Date: \(response?.headers.first(name: "Date") ?? "")\n" text += "Response:\n" do { if let data = try result.get() { text += String(data: data, encoding: .utf8) ?? String(data: data, encoding: .ascii) ?? "\(data)" } } catch { text += "\(error)" } return text } } }
内容的提问来源于stack exchange,提问作者Олег Привалов
相关产品推荐
相关产品推荐

