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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:02:01