SwiftUI MVVM架构下ViewModel无法访问@EnvironmentObject及Core Data集成问题
我帮你拆解下当前遇到的两个核心问题,然后给出具体的解决方案,让你的代码更符合MVVM设计,同时摆脱单例依赖:
问题1:PlayerViewModel无法访问环境对象导致崩溃
你猜的没错,@EnvironmentObject是SwiftUI View层级特有的属性包装器,普通ViewModel类不能直接使用它——这就是点击收藏按钮崩溃的原因。解决思路是依赖注入:把FavouritePlayersViewModel的实例通过初始化参数传递给PlayerViewModel,而不是试图从环境中获取。
具体修改步骤
重构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! ) } }在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单例的依赖,让代码更易于测试和维护。
具体修改步骤
重构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)") } } }在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) } } }简化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

