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

iOS应用ViewModel订阅后数组未更新问题求助

iOS CoreData 数据更新后 ViewModel 未同步问题解决思路

问题分析

  1. 不必要的 Debounce 延迟:ViewModel 中对 savedEntities 的订阅添加了 1 秒 .debounce,会导致界面更新被延迟,容易让你误以为数据未同步。
  2. CoreData 操作线程不统一:初始化时 getHistoryData() 在后台线程执行(container.loadPersistentStores 闭包运行在后台),此时对 @Published 属性的赋值不在主线程,Combine 可能无法正确将更新传递到主线程订阅者。
  3. 手动刷新的可靠性不足:当前依赖手动调用 getHistoryData() 更新数据,一旦遗漏调用就会导致数据不同步,且重复 fetch 的效率较低。

解决方案

1. 移除不必要的 Debounce

删除订阅中的防抖逻辑,添加单条数据只会触发一次更新,不需要延迟:

func addSubscribers(){
    historyDataService.$savedEntities
        .receive(on: RunLoop.main) // 确保在主线程更新 UI 相关数据
        .sink { [weak self] returnData in
            guard let self = self else { return }
            self.historyArray = returnData
            self.updateFilteredContacts()
        }
        .store(in: &cancellables)
}

2. 强制 CoreData 操作在主线程执行

修改 getHistoryData(),确保所有涉及 @Published 属性的操作都在主线程完成:

private func getHistoryData(){
    DispatchQueue.main.async { [weak self] in
        guard let self = self else { return }
        let request = NSFetchRequest<HistoryData>(entityName: self.entityName)
        // 可选:添加排序规则保证数据顺序一致
        request.sortDescriptors = [NSSortDescriptor(keyPath: \HistoryData.time, ascending: false)]
        do {
            self.savedEntities = try self.container.viewContext.fetch(request)
        } catch let error {
            print("## Error fetch: \(error)")
        }
    }
}

3. 监听 CoreData 上下文变化自动刷新

替换手动调用刷新的逻辑,通过监听上下文通知实现自动更新,更可靠高效:

class HistoryCallDataService {
    // ... 原有代码 ...
    
    init() {
        container = NSPersistentContainer(name: containerName)
        container.loadPersistentStores { _, error in
            if let error = error {
                print("## Error loading Core data :\(error)")
            }
            // 添加上下文变化监听
            NotificationCenter.default.addObserver(
                self,
                selector: #selector(contextDidChange(_:)),
                name: NSManagedObjectContext.objectsDidChangeNotification,
                object: self.container.viewContext
            )
            self.getHistoryData()
        }
    }
    
    @objc private func contextDidChange(_ notification: Notification) {
        getHistoryData()
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    // 修改 add 方法,只需保存,监听会自动触发刷新
    func add(firstName: String, lastName: String, telephone: String, callHidden: Bool) {
        let entity = HistoryData(context: container.viewContext)
        
        let date = Date()
        let df = DateFormatter()
        df.dateFormat = "yyyy-MM-dd HH:mm:ss"
        let timeString = df.string(from: date)
        
        entity.firstName = firstName
        entity.lastName = lastName
        entity.telephone = telephone
        entity.time = timeString
        entity.callHidden = callHidden
        save()
    }
    
    // ... 原有代码 ...
}

4. 完善 Delete 方法(补充)

视图中用到了删除逻辑,但 ViewModel 未实现对应方法,补充后确保删除数据也能同步更新:

// 在 HistoryViewModel 中添加
func delete(entity: HistoryData) {
    historyDataService.delete(entity: entity)
}

// 在 HistoryCallDataService 中添加
extension HistoryCallDataService {
    func delete(entity: HistoryData) {
        container.viewContext.delete(entity)
        save()
    }
}

额外优化建议

  • 给 HistoryData 显式声明遵循 Identifiable 协议(NSManagedObject 默认已实现),让 ForEach 用法更规范。
  • 优化 DateFormatter,避免重复创建实例:
private static let dateFormatter: DateFormatter = {
    let df = DateFormatter()
    df.dateFormat = "yyyy-MM-dd HH:mm:ss"
    return df
}()

// 使用时:
let timeString = Self.dateFormatter.string(from: date)

内容的提问来源于stack exchange,提问作者DrikiDev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 08:54:53