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

SwiftUI MVVM架构下ViewModel无法访问@EnvironmentObject及Core Data集成问题

解决SwiftUI/Combine MVVM中环境对象与Core Data依赖问题

我帮你拆解下当前遇到的两个核心问题,然后给出具体的解决方案,让你的代码更符合MVVM设计,同时摆脱单例依赖:

问题1:PlayerViewModel无法访问环境对象导致崩溃

你猜的没错,@EnvironmentObject是SwiftUI View层级特有的属性包装器,普通ViewModel类不能直接使用它——这就是点击收藏按钮崩溃的原因。解决思路是依赖注入:把FavouritePlayersViewModel的实例通过初始化参数传递给PlayerViewModel,而不是试图从环境中获取。

具体修改步骤

  1. 重构PlayerViewModel的初始化方法
    把需要的依赖(收藏VM、Core Data上下文)都作为初始化参数传入:

    class PlayerViewModel: ObservableObject {
        private let favouritePlayersVM: FavouritePlayersViewModel
        private let context: NSManagedObjectContext
        
        // 你的其他属性
        var id: UUID!
        var firstName: String!
        var secondName: String!
        var favourite = false {
            didSet {
                mapToCoreData()
            }
        }
        
        // 初始化时注入依赖
        init(favouritePlayersVM: FavouritePlayersViewModel, 
             context: NSManagedObjectContext,
             id: UUID,
             firstName: String,
             secondName: String) {
            self.favouritePlayersVM = favouritePlayersVM
            self.context = context
            self.id = id
            self.firstName = firstName
            self.secondName = secondName
        }
        
        func mapToCoreData() {
            // 先检查是否已存在该玩家,避免重复创建
            let fetchRequest: NSFetchRequest<PlayerCD> = PlayerCD.fetchRequest()
            fetchRequest.predicate = NSPredicate(format: "id == %@", id as CVarArg)
            
            do {
                let existingPlayers = try context.fetch(fetchRequest)
                let storedPlayer: PlayerCD
                
                if let existing = existingPlayers.first {
                    storedPlayer = existing
                    // 更新属性(如果需要)
                    storedPlayer.firstName = firstName
                    storedPlayer.secondName = secondName
                } else {
                    storedPlayer = PlayerCD(context: context)
                    storedPlayer.id = id
                    storedPlayer.firstName = firstName
                    storedPlayer.secondName = secondName
                }
                
                try context.save()
                favouritePlayersVM.updateFavourites()
            } catch {
                print("Core Data保存失败: \(error.localizedDescription)")
            }
        }
        
        // 重构从Core Data映射的方法
        static func mapFromCoreData(player: PlayerCD, 
                                    favouritePlayersVM: FavouritePlayersViewModel,
                                    context: NSManagedObjectContext) -> PlayerViewModel {
            .init(
                favouritePlayersVM: favouritePlayersVM,
                context: context,
                id: player.id!,
                firstName: player.firstName!,
                secondName: player.secondName!
            )
        }
    }
    
  2. 在View中传递依赖
    在创建PlayerViewModel的地方(比如FetchedPlayersView),从环境中获取需要的实例,再传递给ViewModel:

    struct FetchedPlayersView: View {
        @EnvironmentObject var fetchedResultsVM: FetchedResultsViewModel
        @EnvironmentObject var favouritePlayersVM: FavouritePlayersViewModel
        @Environment(\.managedObjectContext) private var context
        
        var body: some View {
            Section(header: Text("Players")) {
                ForEach(fetchedResultsVM.fetchedPlayers, content: { playerData in
                    // 这里假设playerData是你从网络/其他来源获取的原始数据
                    PlayerView(player: PlayerViewModel(
                        favouritePlayersVM: favouritePlayersVM,
                        context: context,
                        id: playerData.id,
                        firstName: playerData.firstName,
                        secondName: playerData.secondName
                    ))
                })
                if fetchedResultsVM.playersExpandable {
                    MoreResultsButton(action: fetchedResultsVM.getMorePlayers, buttonTitle: "More players")
                }
            }
        }
    }
    

问题2:ViewModel无法获取环境中的NSManagedObjectContext

同样用依赖注入解决:在View中通过@Environment(\.managedObjectContext)获取上下文,再传递给ViewModel,彻底摆脱对PersistenceController单例的依赖,让代码更易于测试和维护。

具体修改步骤

  1. 重构FavouritePlayersViewModel
    让它通过初始化接收上下文,而不是直接调用单例:

    class FavouritePlayersViewModel: ObservableObject {
        @Published var players = [PlayerViewModel]()
        private let context: NSManagedObjectContext
        
        init(context: NSManagedObjectContext) {
            self.context = context
        }
        
        func updateFavourites() {
            do {
                let savedPlayers = try context.fetch(PlayerCD.fetchRequest())
                self.players = savedPlayers.map { 
                    PlayerViewModel.mapFromCoreData(
                        player: $0,
                        favouritePlayersVM: self,
                        context: context
                    )
                }
            } catch {
                print("获取收藏列表失败: \(error.localizedDescription)")
            }
        }
    }
    
  2. 在App入口初始化时注入上下文
    修改Football_GuruApp,把上下文传递给FavouritePlayersViewModel:

    struct Football_GuruApp: App {
        let persistenceController = PersistenceController.shared
        private let context = PersistenceController.shared.container.viewContext
        @StateObject var favouritePlayers: FavouritePlayersViewModel
        
        init() {
            _favouritePlayers = StateObject(wrappedValue: FavouritePlayersViewModel(context: context))
        }
        
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environment(\.managedObjectContext, context)
                    .environmentObject(favouritePlayers)
            }
        }
    }
    
  3. 简化FavouritesView
    让它直接观察FavouritePlayersViewModel的@Published数组,不用自己查询Core Data:

    struct FavouritesView: View {
        @EnvironmentObject var favouritePlayersVM: FavouritePlayersViewModel
        
        var body: some View {
            List {
                ForEach(favouritePlayersVM.players) { player in
                    PlayerView(player: player)
                }
            }
            .onAppear {
                favouritePlayersVM.updateFavourites()
            }
        }
    }
    

核心优化总结

  • 摆脱@EnvironmentObject在ViewModel中的错误使用:依赖注入是ViewModel之间传递依赖的正确方式。
  • 移除Core Data单例依赖:通过上下文注入,让ViewModel更灵活,方便单元测试时替换为内存上下文。
  • 优化Core Data操作:添加重复检查,避免创建重复实体,同时正确处理错误(不要用try?忽略错误)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 21:37:38