无需传递ViewModel,如何在导航栈深处触发父视图更新?
你当前的实现中,子视图修改CoreData数据后父视图无法自动更新,除了传递ViewModel并手动调用loadFoodItems,还有以下几种更优的方案:
方案一:用NSFetchedResultsController自动监听CoreData数据变化
CoreData自带的NSFetchedResultsController可以实时监听指定实体的增删改变化,自动触发UI更新,不需要手动调用刷新方法。
修改FoodListViewModel的实现:
import CoreData import Combine class FoodListViewModel: ObservableObject { private(set) var category: FoodItemCategory @Published var foodItems: [FoodItemViewModel] = [] private var fetchedResultsController: NSFetchedResultsController<FoodItem>! private var cancellables = Set<AnyCancellable>() init(category: FoodItemCategory) { self.category = category setupFetchedResultsController() // 初始加载数据 foodItems = fetchedResultsController.fetchedObjects?.map { FoodItemViewModel(from: $0) } ?? [] } private func setupFetchedResultsController() { let fetchRequest: NSFetchRequest<FoodItem> = FoodItem.fetchRequest() // 添加分类过滤条件 fetchRequest.predicate = NSPredicate(format: "category == %@", category.rawValue) // 可按需添加排序规则 fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \FoodItem.name, ascending: true)] fetchedResultsController = NSFetchedResultsController( fetchRequest: fetchRequest, managedObjectContext: PersistenceController.shared.container.viewContext, sectionNameKeyPath: nil, cacheName: nil ) // 监听控制器的变化通知 NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange) .receive(on: DispatchQueue.main) .sink { [weak self] _ in self?.updateFoodItems() } .store(in: &cancellables) do { try fetchedResultsController.performFetch() } catch { print("Fetch failed: \(error.localizedDescription)") } } private func updateFoodItems() { foodItems = fetchedResultsController.fetchedObjects?.map { FoodItemViewModel(from: $0) } ?? [] } }
优点:完全自动监听CoreData变化,无需子视图做额外操作;缺点:需要处理NSFetchedResultsController的配置,代码量稍多。
方案二:让FoodItemViewModel监听CoreData实体变化
把FoodItemViewModel改成ObservableObject,直接关联CoreData的FoodItem实体,当实体属性变化时自动通知父视图更新对应的列表行。
修改FoodItemViewModel:
class FoodItemViewModel: ObservableObject { @ObservedObject private var foodItem: FoodItem var name: String { foodItem.name ?? "" } // 其他需要暴露的属性 init(from foodItem: FoodItem) { self.foodItem = foodItem } }
此时FoodListViewModel中的foodItems无需手动刷新,因为每个FoodItemViewModel会自动监听对应实体的变化,父视图的List会自动更新单个行,而不是整个列表刷新。
优点:只更新变化的行,性能更优;缺点:需要确保FoodItemViewModel持有CoreData实体的强引用(但CoreData上下文会管理实体生命周期)。
方案三:通过NotificationCenter发送更新通知
在子视图修改CoreData并保存上下文后,发送一个自定义通知,FoodListViewModel监听该通知,收到后调用loadFoodItems刷新数据。
- 定义通知名称:
extension Notification.Name { static let foodItemsUpdated = Notification.Name("foodItemsUpdated") }
- 子视图修改数据后发送通知:
// 子视图中保存CoreData上下文后 do { try PersistenceController.shared.container.viewContext.save() NotificationCenter.default.post(name: .foodItemsUpdated, object: nil) } catch { print("Save failed: \(error.localizedDescription)") }
FoodListViewModel监听通知:
class FoodListViewModel: ObservableObject { private(set) var category: FoodItemCategory @Published var foodItems: [FoodItemViewModel] = [] private var cancellables = Set<AnyCancellable>() init(category: FoodItemCategory) { self.category = category loadFoodItems() // 监听通知 NotificationCenter.default.publisher(for: .foodItemsUpdated) .receive(on: DispatchQueue.main) .sink { [weak self] _ in self?.loadFoodItems() } .store(in: &cancellables) } func loadFoodItems() { foodItems = FoodItem.fetchAll(for: category).map { FoodItemViewModel(from: $0) } } }
优点:子视图无需持有父ViewModel,解耦性强;缺点:需要手动管理通知的发送和监听,注意避免内存泄漏(用weak self)。
内容的提问来源于stack exchange,提问作者Ulrich

