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

SwiftUI中@Published修饰的Array更新后过滤时恢复旧值排查求助

问题根源

你的核心问题是CoinListViewModel的Combine监听链无法收到CoinsRepository内部coins属性的更新通知,导致触发过滤时,Combine的combineLatest operator只能读取到缓存的初始coins值,才会出现状态回退的问题。具体有两处错误:

  1. Firebase数据库的回调默认在后台线程执行,你首次加载coins赋值时没有切换到主线程,@Published属性在非主线程发送的变更通知会被Combine链忽略,导致首次加载后的coins值没有被监听链正确缓存。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 05:36:03