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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 02:57:27