SwiftUI中@Published修饰的Array更新后过滤时恢复旧值排查求助
问题根源
你的核心问题是CoinListViewModel的Combine监听链无法收到CoinsRepository内部coins属性的更新通知,导致触发过滤时,Combine的combineLatest operator只能读取到缓存的初始coins值,才会出现状态回退的问题。具体有两处错误:
- Firebase数据库的回调默认在后台线程执行,你首次加载
coins赋值时没有切换到主线程,@Published属性在非主线程发送的变更通知会被Combine链忽略,导致首次加载后的coins值没有被监听链正确缓存。 updateFavorite方法中手动调用objectWillChange.send()属于多余操作,@Published属性会在值变更前自动发送变更通知,手动调用不会解决监听问题,反而可能导致通知时序异常。
修复步骤
1. 修复CoinsRepository的线程和多余通知问题
修改loadDatabase方法,将赋值操作切换到主线程,同时删除updateFavorite中手动发送通知的代码:
class CoinsRepository: ObservableObject { @Published var coins = [Coin]() var ref: DatabaseReference! init() { self.ref = Database.database().reference() loadDatabase(ref) } func loadDatabase(_ ref: DatabaseReference) { ref.child("coins").observeSingleEvent(of: .value) { snapshot in guard let dictionaries = snapshot.value as? [String: Any] else { return } // 切换到主线程赋值 DispatchQueue.main.async { self.coins = dictionaries.compactMap({ key, value in guard let dic = value as? [String: Any] else { return nil } return Coin(dic) }) } } } func updateFavorite(_ coin: Coin, state: Bool) { let path = ref.child("coins/\(coin.name)") path.updateChildValues(["favorite": state]) { err, ref in if let err = err { print("ERROR: \(err.localizedDescription)") return } DispatchQueue.main.async { // 直接修改coins数组即可,@Published会自动发送通知 guard let index = self.coins.firstIndex(where: { $0.name == coin.name }) else { return } self.coins[index].favorite = state } } } }
2. 优化CoinListViewModel的Combine监听链
首先不需要给coinRepository加@Published注解,因为你不会替换整个Repository实例,其次给监听链加上主线程接收的修饰符,确保所有变更都在主线程处理:
class CoinListViewModel: ObservableObject { // 去掉@Published,直接持有Repository实例 let coinRepository = CoinsRepository() @Published var coinCellViewModels = [CoinCellViewModel]() @Published var filterBy: [Bool] = UserDefaults.standard.array(forKey: "filter") as? [Bool] ?? [false, false, false] @Published var fbPrice: Double = 0.00 @Published var searchText: String = "" private var cancellables = Set<AnyCancellable>() init() { $searchText // 给coins的发布者加上主线程接收修饰符 .combineLatest(coinRepository.$coins.receive(on: DispatchQueue.main), $fbPrice, $filterBy) .map(filter) .sink { [weak self] coins in guard let self = self else { return } self.coinCellViewModels = coins.map { CoinCellViewModel(coin: $0) } } .store(in: &cancellables) } // 注意你的filter方法要直接使用combineLatest传入的四个参数,不要在方法内部访问self.coinRepository.coins private func filter(searchText: String, coins: [Coin], fbPrice: Double, filterBy: [Bool]) -> [Coin] { // 你的过滤逻辑写在这里,直接用传入的coins参数即可 } }
内容的提问来源于stack exchange,提问作者gavisio
相关产品推荐
相关产品推荐

