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

使用多SortDescriptor的@Query导致SwiftData崩溃问题咨询

SwiftData 关联属性排序崩溃问题

环境

  • Xcode Version 15.0 beta (15A5160n)
  • 测试环境:macOS Sonoma beta、iOS 17.0 iPhone 14 Pro模拟器

重现步骤

  1. 将下方代码导入SwiftData新项目
  2. 构建并运行应用
  3. 首次运行点击「Setup」按钮初始化SwiftData容器数据
  4. 当前@Query仅按Attribute.order排序,可正常展示所有Attribute对象
  5. 注释单条件排序的@Query,取消注释包含两个SortDescriptor的@Query(按Attribute.item.order和Attribute.order排序)
  6. 重新运行应用直接崩溃,控制台报错:

SwiftData/DataUtilities.swift:1179: Fatal error: Unexpected type for Expansion: Item

预期效果

Attribute按所属Item的Item.order分组,组内按Attribute.order排序,展示格式如下:

Item[0] Attribute[0]
Item[0] Attribute[1]
Item[0] Attribute[2]
Item[1] Attribute[0]
Item[1] Attribute[1]
Item[1] Attribute[2]
Item[2] Attribute[0]
Item[2] Attribute[1]
Item[2] Attribute[2]

疑问

该排序模式在Core Data中可正常工作,请问此用法是否合理?有无可行的解决方案?

测试代码

import SwiftUI
import SwiftData

@Model
final class Item {
    
    var name: String
    var order: Int
    @Relationship(.cascade) var attributes: [Attribute] = []
    
    init(name: String, order: Int) {
        self.name = name
        self.order = order
    }
}

@Model
final class Attribute {
    
    var name: String
    var order: Int
    @Relationship var item: Item

    init(item: Item, name: String, order: Int) {
        self.item = item
        self.name = name
        self.order = order
    }
}

struct ContentView: View {
    
    @Environment(.modelContext) private var modelContext
    
    @Query(FetchDescriptor<Attribute>(sortBy: [SortDescriptor(\Attribute.order, order: .forward)]))
    //    @Query(FetchDescriptor<Attribute>(sortBy: [SortDescriptor(\Attribute.item.order, order: .forward),
    //                                               SortDescriptor(\Attribute.order, order: .forward)]))
    private var attributes: [Attribute]
    
    var body: some View {
        NavigationView {
            List {
                ForEach(self.attributes) { attribute in
                    Text("Item[\(attribute.item.order)] Attribute[\(attribute.order)]")
                        .monospaced()
                }
            }
            .toolbar {
                ToolbarItem {
                    Button("Setup", action: self.setup)
                }
            }
        }
    }

    private func setup() {
        withAnimation {
            do {
                
                let attributeDescriptor = FetchDescriptor<Attribute>()
                let attributes = try self.modelContext.fetch(attributeDescriptor)
                for attribute in attributes {
                    self.modelContext.delete(attribute)
                }
                
                let itemDescriptor = FetchDescriptor<Item>()
                let items = try self.modelContext.fetch(itemDescriptor)
                for item in items {
                    self.modelContext.delete(item)
                }
                try self.modelContext.save()

                let item1 = Item(name: "Z", order: 0)
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).0", order: 0))
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).1", order: 1))
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).2", order: 2))
                self.modelContext.insert(item1)

                let item2 = Item(name: "Y", order: 1)
                self.modelContext.insert(Attribute(item: item2, name: "\(item2.name).0", order: 0))
                self.modelContext.insert(Attribute(item: item2, name: "\(item2.name).1", order: 1))
                self.modelContext.insert(Attribute(item: item2, name: "\(item2.name).2", order: 2))
                self.modelContext.insert(item2)

                let item3 = Item(name: "X", order: 2)
                self.modelContext.insert(Attribute(item: item3, name: "\(item3.name).0", order: 0))
                self.modelContext.insert(Attribute(item: item3, name: "\(item3.name).1", order: 1))
                self.modelContext.insert(Attribute(item: item3, name: "\(item3.name).2", order: 2))
                self.modelContext.insert(item2)

                try self.modelContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }

}

解决方案与分析

用法合理性

你的用法逻辑上完全合理,这是关联属性排序的常见业务场景,Core Data支持此类操作也证明了需求的合法性。当前SwiftData的崩溃属于beta版本的已知bug,你提交反馈的做法是正确的。

可行解决方案

目前有三种方式可以绕过这个bug:

方案1:内存中二次排序

保持原单条件@Query获取所有Attribute数据,在视图中通过计算属性完成内存排序:

@Query(FetchDescriptor<Attribute>(sortBy: [SortDescriptor(\Attribute.order, order: .forward)]))
private var attributes: [Attribute]

// 计算属性实现目标排序逻辑
private var sortedAttributes: [Attribute] {
    attributes.sorted {
        if $0.item.order != $1.item.order {
            return $0.item.order < $1.item.order
        }
        return $0.order < $1.order
    }
}

// 视图中替换为sortedAttributes
List {
    ForEach(sortedAttributes) { attribute in
        Text("Item[\(attribute.item.order)] Attribute[\(attribute.order)]")
            .monospaced()
    }
}

方案2:反向查询+嵌套展示

通过查询Item并按Item.order排序,再嵌套展示其关联的Attribute(组内按Attribute.order排序),这种方式更贴合SwiftData的关联数据设计思路:

// 修改查询对象为Item
@Query(FetchDescriptor<Item>(sortBy: [SortDescriptor(\Item.order, order: .forward)]))
private var items: [Item]

// 视图中嵌套展示分组数据
List {
    ForEach(items) { item in
        Section(header: Text("Item[\(item.order)]")) {
            ForEach(item.attributes.sorted(by: { $0.order < $1.order })) { attribute in
                Text("Attribute[\(attribute.order)]")
                    .monospaced()
            }
        }
    }
}

方案3:显式预加载关联对象(进阶)

如果需要在数据库层面完成排序,可以尝试手动构建FetchDescriptor并显式指定预加载关联的Item,部分beta版本中此方法可规避崩溃:

// 自定义FetchDescriptor
var fetchDescriptor: FetchDescriptor<Attribute> {
    let descriptor = FetchDescriptor<Attribute>()
    descriptor.sortBy = [
        SortDescriptor(\Attribute.item.order),
        SortDescriptor(\Attribute.order)
    ]
    // 显式预加载关联的Item,避免延迟加载引发的类型错误
    descriptor.relationshipKeyPathsForPrefetching = [\Attribute.item]
    return descriptor
}

@Query(fetchDescriptor) private var attributes: [Attribute]

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 14:14:57