SwiftUI中如何重新实例化EnvironmentObject解决用户切换后旧数据残留问题
问题核心原因
BusinessAddressRepository的快照监听器仅在首次初始化时创建,用户切换后不会重新绑定新用户的地址集合,也未清理旧监听器和历史数据BusinessAddressViewModel作为全局@StateObject初始化,应用生命周期内仅创建一次,始终持有首次生成的Repository实例
推荐解决方案
方案1:调整视图模型注入层级(最优)
将用户相关的视图模型下沉到登录后的视图层级,利用SwiftUI视图生命周期自动销毁重建实例,无需手动维护重置逻辑:
- 先修改
BusinessAddressRepository,补充清理逻辑,修复监听器重复绑定问题
class BusinessAddressRepository: ObservableObject { let db = Firestore.firestore() private var snapshotListener: ListenerRegistration? @Published var businessAddresses = [BusinessAddress]() init() { startSnapshotListener() } // 新增:清理旧监听器和数据 func reset() { snapshotListener?.remove() snapshotListener = nil businessAddresses.removeAll() } func startSnapshotListener() { guard let currentUserId = Auth.auth().currentUser?.uid else { reset() // 未登录时直接清空数据 return } // 先清理旧监听器,避免同时监听多个用户的集合 reset() // 新增[weak self]避免循环引用 self.snapshotListener = db.collection(FirestoreCollection.users).document(currentUserId).collection(FirestoreCollection.businessAddresses).addSnapshotListener { [weak self] (querySnapshot, error) in guard let self = self else { return } if let error = error { print("Error getting documents: \(error)") } else { guard let documents = querySnapshot?.documents else { print("No Business Addresses.") return } self.businessAddresses = documents.compactMap { businessAddress in do { print("This is a business address *********** \(businessAddress.documentID)") return try businessAddress.data(as: BusinessAddress.self) } catch { print(error) } return nil } } } } }
- 新增根视图负责判断登录状态,登录后才创建用户相关视图模型
struct RootView: View { @State private var isLoggedIn = Auth.auth().currentUser != nil init() { // 监听Firebase Auth状态变化 Auth.auth().addStateDidChangeListener { _, user in isLoggedIn = user != nil } } var body: some View { if isLoggedIn { // 你的登录后主视图(比如TabView) MainTabView() // 登录后才初始化视图模型,登出时视图销毁,实例自动释放 .environmentObject(BusinessAddressViewModel(businessAddressRepository: BusinessAddressRepository())) } else { LoginView() } } }
- 修改App入口,移除全局的视图模型初始化
@main struct YourApp: App { var body: some Scene { WindowGroup { RootView() } } }
方案2:手动重置全局实例(适合无需调整现有路由的场景)
如果不想修改现有路由结构,可以手动监听Auth状态,切换用户时重置实例:
- 完成方案1中第一步的
BusinessAddressRepository修改 - 给
BusinessAddressViewModel新增重置方法
class BusinessAddressViewModel: ObservableObject { var businessAddressRepository: BusinessAddressRepository @Published var businessAddressRowViewModels = [BusinessAddressRowViewModel]() private var cancellables = Set<AnyCancellable>() init(businessAddressRepository: BusinessAddressRepository) { self.businessAddressRepository = businessAddressRepository self.startCombine() } // 新增:重置方法 func reset(with newRepo: BusinessAddressRepository) { cancellables.removeAll() // 清空旧的Combine订阅 businessAddressRepository = newRepo businessAddressRowViewModels.removeAll() startCombine() // 重新绑定新仓库的数据流 } func startCombine() { businessAddressRepository .$businessAddresses .receive(on: RunLoop.main) .map { businessAddress in businessAddress.map { BusinessAddressRowViewModel(businessAddress: $0) } } .assign(to: \.businessAddressRowViewModels, on: self) .store(in: &cancellables) } }
- 在App入口监听Auth状态,切换用户时触发重置
@main struct YourApp: App { @StateObject private var businessAddressViewModel = BusinessAddressViewModel(businessAddressRepository: BusinessAddressRepository()) init() { Auth.auth().addStateDidChangeListener { [weak self] _, user in guard let self = self else { return } if let _ = user { // 新用户登录,传入全新的仓库实例重置视图模型 self.businessAddressViewModel.reset(with: BusinessAddressRepository()) } else { // 用户登出,清空所有数据 self.businessAddressViewModel.businessAddressRepository.reset() self.businessAddressViewModel.businessAddressRowViewModels.removeAll() } } } var body: some Scene { WindowGroup { ContentView() .environmentObject(businessAddressViewModel) } } }
内容的提问来源于stack exchange,提问作者jonthornham
相关产品推荐
相关产品推荐

