NavigationLink跳转崩溃:关联Core Data的NavigationDestination触发异常
问题描述
我正在开发一款基于SwiftUI和Core Data的应用,使用NavigationStack与NavigationLink构建导航框架,目前使用无数据依赖的占位视图。但遇到如下问题:
初始视图 → 占位视图A(点击NavigationLink失败)→ 占位视图B → 接收Core Data查询结果作为参数的视图。
占位视图B中的NavigationDestination指向关联Core Data参数的目标视图,点击NavigationLink后界面冻结,内存占用持续上升,CPU使用率跳至99%。移除SecondView中results[0].property改用普通文本视图则恢复正常;将关联Core Data参数的目标视图移至初始视图也可正常运行。
可复现错误的简化代码:
import Foundation import CoreData import SwiftUI @main struct TestCrashAppApp: App { @StateObject private var dataController: DataController = DataController() @StateObject var navigationHandler = NavigationHandler() var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, dataController.container.viewContext) .environmentObject(navigationHandler) } } } struct ContentView: View { @EnvironmentObject var pathManager: NavigationHandler @FetchRequest(sortDescriptors: []) var results: FetchedResults<MyEntity> var body: some View { NavigationStack { VStack { NavigationLink("Test Link", value: 1) } .padding() .navigationDestination(for: Int.self) { int in SecondView() } } } } struct SecondView: View { @FetchRequest(sortDescriptors: []) var results: FetchedResults<MyEntity> var body: some View { NavigationLink("Test Other Link", value: "") .navigationDestination(for: String.self) { str in Text("\(results[0].property)") } } } class DataController: ObservableObject { let container = NSPersistentContainer(name: "TestModel") init() { container.loadPersistentStores { description, error in if let error = error { print("Core Data failed to load: \(error.localizedDescription)") return } self.container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump } } } class NavigationHandler: ObservableObject { @Published var path = NavigationPath() func reset() { path = NavigationPath() } func goBack() { path.removeLast(1) } }
注:TestModel包含一个实体MyEntity,带有单个Int16类型的属性property。
解决方案
核心问题原因
navigationDestination的闭包在初始化时会尝试解析results[0].property,这个操作会触发SwiftUI的视图更新循环,再加上直接访问数组索引可能引发的潜在数据加载问题,最终导致无限刷新、资源耗尽。
方案一:传递Core Data实体作为导航值
- 让Core Data实体遵循
Hashable协议(NavigationLink的value要求可哈希):
extension MyEntity: Hashable { public static func == (lhs: MyEntity, rhs: MyEntity) -> Bool { lhs.objectID == rhs.objectID } public func hash(into hasher: inout Hasher) { hasher.combine(objectID) } }
- 修改SecondView,先判断数据存在性,再传递实体:
struct SecondView: View { @FetchRequest(sortDescriptors: []) var results: FetchedResults<MyEntity> var body: some View { if let targetEntity = results.first { NavigationLink("Test Other Link", value: targetEntity) .navigationDestination(for: MyEntity.self) { entity in Text("\(entity.property)") } } else { Text("暂无数据") } } }
方案二:在目标视图中独立执行Core Data查询
将依赖Core Data的逻辑移到独立的目标视图中,避免在navigationDestination闭包中直接访问FetchedResults:
// 新建独立的目标视图 struct FinalView: View { @FetchRequest(sortDescriptors: []) var results: FetchedResults<MyEntity> var body: some View { if let entity = results.first { Text("\(entity.property)") } else { Text("暂无数据") } } } // 修改SecondView struct SecondView: View { var body: some View { NavigationLink("Test Other Link", value: "") .navigationDestination(for: String.self) { str in FinalView() } } }
关键注意事项
- 永远不要直接使用
results[0]访问Core Data查询结果,必须先通过results.first判断数据是否存在,避免数组越界错误。 - 避免在
navigationDestination闭包中执行依赖状态或异步数据的操作,闭包的求值逻辑会被SwiftUI提前处理,容易触发无限刷新循环。
内容的提问来源于stack exchange,提问作者user67893453
相关产品推荐
相关产品推荐

