Swift中跨多函数使用异步数据的正确处理方法咨询
嘿,这个问题提得特别到位——重复发起网络请求不仅浪费带宽、拖慢性能,还可能导致数据不一致,确实得优化。我给你几个iOS/Swift开发里成熟的方案,都是实际项目中常用的:
核心思路:缓存+统一数据管理
本质是让数据只请求一次,后续所有依赖函数复用已获取的缓存数据,同时兼顾数据新鲜度和错误处理。
1. 给DataManager加缓存+单例模式
这是最直接的方案:把DataManager做成单例,内部缓存请求到的数据,下次调用先判断缓存是否有效,有效就直接返回,无效才发起新请求。
代码示例:
class DataManager { // 单例实例,全局共享 static let shared = DataManager() // 缓存的数据 private var cachedItems: [Model]? // 记录上次请求时间,用于判断缓存是否过期 private var lastFetchTime: Date? // 缓存过期时间(比如1小时) private let cacheExpireInterval: TimeInterval = 3600 // 禁止外部实例化,保证单例唯一性 private init() {} func requestData(completion: @escaping ([Model]?) -> Void) { // 先检查缓存是否有效 cacheQueue.sync { [weak self] in guard let self = self, let items = self.cachedItems, let time = self.lastFetchTime, Date().timeIntervalSince(time) < self.cacheExpireInterval else { // 缓存无效,发起网络请求 self?.fetchFromNetwork(completion: completion) return } // 返回缓存数据 completion(items) } } // 手动刷新缓存的方法(比如下拉刷新时调用) func refreshData(completion: @escaping ([Model]?) -> Void) { cacheQueue.sync { [weak self] in self?.cachedItems = nil self?.lastFetchTime = nil } requestData(completion: completion) } // 私有方法:发起网络请求并更新缓存 private func fetchFromNetwork(completion: @escaping ([Model]?) -> Void) { guard let url = URL(string: "your-api-endpoint") else { completion(nil) return } URLSession.shared.dataTask(with: url) { [weak self] data, _, error in guard let data = data, error == nil else { completion(nil) return } do { let items = try JSONDecoder().decode([Model].self, from: data) // 线程安全地更新缓存 self?.cacheQueue.async { self?.cachedItems = items self?.lastFetchTime = Date() } completion(items) } catch { completion(nil) } }.resume() } // 用于缓存读写的串行队列,避免多线程数据竞争 private let cacheQueue = DispatchQueue(label: "com.yourapp.datamanager.cache") }
使用方式:
所有依赖函数直接调用单例的requestData,不用每次新建DataManager实例:
func generateRandomItem() { DataManager.shared.requestData { items in guard let items = items else { return } let randomItem = items.randomElement() // 处理随机项逻辑 } } func listAlphabetizedItems() { DataManager.shared.requestData { items in guard let items = items else { return } let sortedItems = items.sorted { $0.name < $1.name } // 处理排序列表逻辑 } }
2. 用Async/Await替代Completion Handler(iOS 15+)
如果你的项目支持iOS 15及以上,用Async/Await能让代码更简洁,缓存逻辑和单例模式可以完美结合,还能避免回调地狱。
代码示例:
class DataManager { static let shared = DataManager() private var cachedItems: [Model]? private var lastFetchTime: Date? private let cacheExpireInterval: TimeInterval = 3600 private let cacheQueue = DispatchQueue(label: "com.yourapp.datamanager.cache") private init() {} func fetchData() async throws -> [Model] { // 检查缓存 let cachedResult = try await cacheQueue.sync { () throws -> [Model]? in guard let items = cachedItems, let time = lastFetchTime, Date().timeIntervalSince(time) < cacheExpireInterval else { return nil } return items } if let items = cachedResult { return items } // 缓存无效,发起网络请求 guard let url = URL(string: "your-api-endpoint") else { throw NetworkError.invalidURL } let (data, _) = try await URLSession.shared.data(from: url) let items = try JSONDecoder().decode([Model].self, from: data) // 更新缓存 try await cacheQueue.async { self.cachedItems = items self.lastFetchTime = Date() } return items } func refreshData() async throws -> [Model] { try await cacheQueue.async { self.cachedItems = nil self.lastFetchTime = nil } return try await fetchData() } enum NetworkError: Error { case invalidURL } }
使用方式:
func generateRandomItem() async { do { let items = try await DataManager.shared.fetchData() let randomItem = items.randomElement() // 处理逻辑 } catch { // 错误处理 print("获取数据失败:\(error)") } }
3. 数据状态管理(SwiftUI场景)
如果是SwiftUI项目,可以把数据放到ObservableObject中,让所有依赖的视图自动监听数据变化,数据只加载一次,全局复用。
代码示例:
class DataStore: ObservableObject { @Published var items: [Model] = [] @Published var isLoading = false @Published var error: Error? private let dataManager = DataManager.shared func loadData() { // 避免重复加载 guard items.isEmpty, !isLoading else { return } isLoading = true dataManager.requestData { [weak self] result in DispatchQueue.main.async { self?.isLoading = false if let items = result { self?.items = items } else { self?.error = NetworkError.failedToFetch } } } } enum NetworkError: Error { case failedToFetch } }
使用方式:
在视图中注入DataStore,调用loadData后,所有用到items的地方都会自动更新:
struct ContentView: View { @StateObject private var dataStore = DataStore() var body: some View { List(dataStore.items) { item in Text(item.name) } .onAppear { dataStore.loadData() } .overlay { if dataStore.isLoading { ProgressView() } } } }
4. 依赖注入(避免单例耦合)
如果不想用单例(比如单元测试时需要Mock数据),可以通过依赖注入把DataManager实例传递给需要的类/函数,保证所有依赖方共享同一个实例,从而复用缓存。
代码示例:
class ItemService { private let dataManager: DataManager // 通过构造函数注入DataManager实例 init(dataManager: DataManager) { self.dataManager = dataManager } func generateRandomItem(completion: @escaping (Model?) -> Void) { dataManager.requestData { items in completion(items?.randomElement()) } } func listAlphabetizedItems(completion: @escaping ([Model]?) -> Void) { dataManager.requestData { items in completion(items?.sorted { $0.name < $1.name }) } } }
使用方式:
// 创建共享的DataManager实例 let sharedDataManager = DataManager() // 注入到ItemService let itemService = ItemService(dataManager: sharedDataManager) // 调用方法 itemService.generateRandomItem { item in // 处理逻辑 } itemService.listAlphabetizedItems { items in // 处理逻辑 }
额外注意点
- 缓存策略:根据业务需求调整过期时间,比如实时性高的数据可以缩短过期时间,或者支持手动刷新。
- 线程安全:缓存的读写一定要放在串行队列中,避免多线程环境下的数据竞争(前面的示例已经处理了这一点)。
- 错误处理:要考虑缓存不存在且网络请求失败的情况,给用户友好提示或降级逻辑。
内容的提问来源于stack exchange,提问作者user2023106
相关产品推荐
相关产品推荐

