SwiftUI详情视图中带Predicate的@Query引发内存飙升问题
SwiftUI SwiftData详情视图@Query带Predicate导致卡顿与内存泄漏问题
在SwiftUI+SwiftData项目中,当在详情视图(DetailView)中使用带有自定义#Predicate的@Query时,点击列表项进入详情视图后,应用会出现卡顿,内存占用还会持续线性增长。但把带Predicate的@Query放在主列表视图(ContentView)中时,应用运行完全正常。
问题复现代码
详情视图(问题触发处)
struct DetailView: View { var item: Item // 只要设置带过滤条件的Query就会触发问题,无过滤时正常 // Predicate内容只要合法就会触发,这里示例用恒为true的条件 @Query(filter: #Predicate<Item> { item in true }) private var items: [Item] var body: some View { VStack { Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") Text("Count: \(items.count)") } } }
列表视图
struct ContentView: View { @Environment(\.modelContext) private var modelContext // 在此处修改Query添加Predicate无任何问题 @Query private var items: [Item] var body: some View { NavigationSplitView { List { ForEach(items) { item in NavigationLink { DetailView(item: item) } label: { Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } } } detail: { Text("Select an item") } } private func addItem() { withAnimation { let newItem = Item(timestamp: Date()) modelContext.insert(newItem) } } }
数据模型
@Model final class Item { var timestamp: Date init(timestamp: Date) { self.timestamp = timestamp } }
App入口代码
@main struct TestSwiftDataApp: App { var sharedModelContainer: ModelContainer = { let schema = Schema([ Item.self, ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() var body: some Scene { WindowGroup { ContentView() } .modelContainer(sharedModelContainer) } }
问题原因
这是SwiftData的已知问题:当在动态创建的视图(比如NavigationLink跳转的详情视图)中使用固定Predicate的@Query时,SwiftUI会为每个进入的详情视图实例创建独立的查询订阅,且这些订阅无法被正确释放,最终导致内存泄漏和UI卡顿。而主视图中的@Query因为是单例视图实例,订阅只会创建一次,所以不存在这个问题。
解决方案
方案1:查询逻辑移至父视图,参数传递结果
把带Predicate的查询放在主视图中,将需要的数据通过参数传给详情视图,避免在详情视图中创建@Query:
修改ContentView:
struct ContentView: View { @Environment(\.modelContext) private var modelContext // 添加Predicate到主视图的Query中 @Query(filter: #Predicate<Item> { _ in true }) private var items: [Item] var body: some View { NavigationSplitView { List { ForEach(items) { item in NavigationLink { // 将查询结果传递给详情视图 DetailView(item: item, totalCount: items.count) } label: { Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } } } detail: { Text("Select an item") } } private func addItem() { withAnimation { let newItem = Item(timestamp: Date()) modelContext.insert(newItem) } } }
修改DetailView:
struct DetailView: View { var item: Item var totalCount: Int // 接收父视图传递的结果 var body: some View { VStack { Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") Text("Count: \(totalCount)") } } }
方案2:手动通过modelContext执行查询
如果必须在详情视图中执行查询,直接用modelContext手动获取数据,避开@Query:
struct DetailView: View { var item: Item @Environment(\.modelContext) private var modelContext @State private var totalCount = 0 var body: some View { VStack { Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") Text("Count: \(totalCount)") } .onAppear { // 手动执行查询 do { let items = try modelContext.fetch(FetchDescriptor<Item>(predicate: #Predicate<Item> { _ in true })) totalCount = items.count } catch { print("Fetch error: \(error)") } } } }
方案3:使用动态Predicate绑定(支持响应式更新)
如果需要详情视图的查询结果响应数据变化,可以把Predicate绑定到父视图的状态,确保查询订阅被正确管理:
修改ContentView:
struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] @State private var queryPredicate = #Predicate<Item> { _ in true } // 共享Predicate var body: some View { NavigationSplitView { List { ForEach(items) { item in NavigationLink { DetailView(item: item, predicate: $queryPredicate) } label: { Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } } } detail: { Text("Select an item") } } private func addItem() { withAnimation { let newItem = Item(timestamp: Date()) modelContext.insert(newItem) } } }
修改DetailView:
struct DetailView: View { var item: Item @Binding var predicate: Predicate<Item> // 使用绑定的Predicate创建Query @Query(filter: \.predicate) private var items: [Item] var body: some View { VStack { Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") Text("Count: \(items.count)") } } }
内容的提问来源于stack exchange,提问作者Ezekiel
相关产品推荐
相关产品推荐

