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

iOS15真机运行SwiftUI时List出现非预期行为问题求助

问题排查与解决方案

核心原因

该问题是iOS15对SwiftUI ForEach的ID唯一性校验逻辑比旧版本更严格,且对短时间内的连续数组更新敏感度更高触发的,常见诱因如下:

  • 每次onAppear调用startCombine时未清空旧订阅,导致多个订阅同时生效,offerRowViewModels被多次重复赋值,短时间内的连续更新被ForEach的Diffing算法误判存在重复ID
  • OfferRowViewModel的id属性未绑定到Offer的唯一Firestore文档ID,重复初始化同一份Offer对应的OfferRowViewModel时生成了不同的ID,或者存在ID复用逻辑
  • Firestore先返回本地缓存数据、再返回云端最新数据,两次数据更新间隔极短,触发iOS15ForEach的校验bug

修复步骤

1. 解决重复订阅问题

在startCombine方法开头清空已有订阅,避免多个订阅同时触发赋值,同时可加防抖逻辑过滤短时间的连续更新:

func startCombine(currentUserUid: String) {
    // 新增:清空旧订阅,避免重复触发
    self.cancellables.removeAll()
    
    offerRepository
        .$offers
        .receive(on: RunLoop.main)
        // 可选新增:加防抖,过滤100ms内的连续更新,避免缓存+云端两次更新的影响
        .debounce(for: .milliseconds(100), scheduler: RunLoop.main)
        .map { offers in
            offers
                .filter { offer in
                    (currentUserUid != "" ? offer.userId == currentUserUid : false)
                }
                .map { offer in
                    OfferRowViewModel(offer: offer, listingRepository: ListingRepository())
                }
        }
        // 新增:赋值前校验ID唯一性,兜底避免重复
        .map { viewModels in
            var uniqueIds = Set<String>()
            return viewModels.filter { viewModel in
                guard !uniqueIds.contains(viewModel.id) else {
                    print("发现重复ID:\(viewModel.id),已过滤")
                    return false
                }
                uniqueIds.insert(viewModel.id)
                return true
            }
        }
        .assign(to: \.offerRowViewModels, on: self)
        .store(in: &cancellables)
}

2. 确认OfferRowViewModel的ID稳定性

检查OfferRowViewModel的id属性实现,确保直接绑定到对应Offer的Firestore唯一文档ID,不要使用每次初始化生成的UUID()作为id:

// 正确示例:OfferRowViewModel的id绑定到原始Offer的唯一ID
class OfferRowViewModel: Identifiable {
    let id: String
    let offer: Offer
    
    init(offer: Offer, listingRepository: ListingRepository) {
        // 直接使用Offer的文档ID,不要自己生成UUID
        self.id = offer.id
        self.offer = offer
        // 其他初始化逻辑
    }
}

3. 优化onAppear调用逻辑

避免currentUserUid为空时触发无效请求,可增加判断:

.onAppear(perform: {
    guard !authSession.currentUserUid.isEmpty else { return }
    self.offerHistoryViewModel.startCombine(currentUserUid: self.authSession.currentUserUid)
})

验证方法

在赋值给offerRowViewModels之前增加ID重复校验打印,确认是否存在中间态的重复ID:

.map { viewModels in
    let allIds = viewModels.map(\.id)
    let duplicateCount = allIds.count - Set(allIds).count
    if duplicateCount > 0 {
        print("检测到\(duplicateCount)个重复ID,ID列表:\(allIds)")
    }
    return viewModels
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 17:36:03