You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何针对响应错误实现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,提问作者Олег Привалов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 19:05:21