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

SwiftData @Query使用枚举或关联关系筛选/排序时崩溃

SwiftData @Query 崩溃问题:自定义枚举/可转换类型处理方案

问题重现

当使用SwiftData的@Query对非标准支持的数据类型(如自定义枚举、带值转换器的UIColor)进行筛选或排序时,会触发致命崩溃,报错信息:

SwiftData/DataUtilities.swift:1140: Fatal error: Unexpected type for Expansion: Optional<UIColor>

崩溃发生在访问_items.wrappedValue时,仅使用字符串、布尔值、日期等基础类型时无异常,推测是SwiftData的底层查询解析bug。

涉及的核心场景:

  • 基于可选枚举Priority的排序/筛选
  • 基于关联对象Category的链式属性访问(如Item.category?.title)
  • 关联对象中包含带值转换器的UIColor属性

原因分析

SwiftData当前对以下场景的查询解析存在缺陷:

  1. 可选枚举类型的直接比较、链式rawValue访问
  2. 可选关联对象的链式属性访问(如?.title)
  3. 带值转换器的可选类型(如Optional<UIColor>)会干扰查询解析流程,即使查询未直接涉及该属性

解决方案

1. 枚举类型的排序与筛选优化

排序处理

避免直接对可选枚举的rawValue创建SortDescriptor,改为在模型中添加计算属性统一处理nil情况:

@Model
final class Item: Codable {
    // ... 原有属性 ...
    var priority: Priority?
    
    // 添加计算属性,给nil枚举一个默认rawValue
    var prioritySortValue: Int {
        priority?.rawValue ?? -1 // nil值会排在所有枚举值之前
    }
}

排序时使用该计算属性:

func sort() -> [SortDescriptor<Item>]{
    switch self {
    // ... 其他case ...
    case .priority:
        [SortDescriptor(\Item.prioritySortValue)]
    }
}

筛选处理

将枚举比较转为rawValue的数值比较,避免直接比较枚举实例:

let highPriority = Priority.high
if let query {
    return #Predicate {
        $0.priority?.rawValue == highPriority.rawValue &&
        ($0.title.contains(query) || $0.categoryTitle.contains(query)) &&
        $0.isArchived == false
    }
}

2. 关联属性链式访问优化

对Item.category?.title的访问,同样通过计算属性消除可选链式调用:

@Model
final class Item: Codable {
    // ... 原有属性 ...
    @Relationship(deleteRule: .nullify, inverse: \Category.items)
    var category: Category?
    
    // 添加计算属性,处理category为nil的情况
    var categoryTitle: String {
        category?.title ?? ""
    }
}

排序和筛选时直接使用该计算属性:

// 排序
case .category:
    [SortDescriptor(\Item.categoryTitle)]

// 筛选
$0.categoryTitle.contains(query)

3. 移除值转换器改用基础类型存储

针对Category中的UIColor属性,放弃使用值转换器,改为存储Hex字符串(基础类型),通过计算属性转换为UIColor:

@Model
class Category: Codable {
    @Attribute(.unique)
    var title: String
    var items: [Item]?
    
    // 存储Hex字符串,替代值转换器
    var colorHex: String?
    
    // 计算属性处理UIColor与Hex的转换
    var color: UIColor? {
        get {
            guard let hex = colorHex else { return nil }
            let scanner = Scanner(string: hex.replacingOccurrences(of: "#", with: ""))
            var hexNumber: UInt64 = 0
            guard scanner.scanHexInt64(&hexNumber) else { return nil }
            let r = CGFloat((hexNumber & 0xff0000) >> 16) / 255
            let g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255
            let b = CGFloat(hexNumber & 0x0000ff) / 255
            return UIColor(red: r, green: g, blue: b, alpha: 1)
        }
        set {
            guard let color = newValue else {
                colorHex = nil
                return
            }
            var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
            color.getRed(&r, green: &g, blue: &b, alpha: &a)
            let hex = String(format: "#%02lX%02lX%02lX", lroundf(Float(r * 255)), lroundf(Float(g * 255)), lroundf(Float(b * 255)))
            colorHex = hex
        }
    }
    
    // 调整初始化方法
    init(title: String = "", color: UIColor) {
        self.title = title
        self.color = color
    }
    
    // Codable实现调整(仅处理title和colorHex)
    enum CodingKeys: String, CodingKey {
        case title, colorHex
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.title = try container.decode(String.self, forKey: .title)
        self.colorHex = try container.decodeIfPresent(String.self, forKey: .colorHex) ?? UIColor(possibleColors.randomElement()!)?.hexString
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(title, forKey: .title)
        try container.encode(colorHex, forKey: .colorHex)
    }
}

这种方式完全使用SwiftData原生支持的字符串类型存储颜色,避免值转换器带来的查询解析问题。

验证结果

修改后,基于枚举、关联属性的排序和筛选操作均能正常执行,不再触发崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 11:59:55