
引子
在 WWDC26 中,SwiftData 新增了 @Attribute(.codable) 选项。它的任务很明确:
当 SwiftData 不知道如何把某个
Codable类型转换成自己的持久化 schema 时,让该类型自行编码,再由 SwiftData 保存编码后的表示。
先纠正一个常见叫法:.codable 并不是 Swift 语言的"关键词",而是 SwiftData @Attribute 宏的一个选项。
swift
@Attribute(.codable)
var value: SomeCodableType
它解决的不是"这个类型有没有泛型",而是"SwiftData 能不能原生理解并建模这个类型"。
到底 .codable 选项有什么玄妙之处又有哪些陷阱和坑呢?
让我们马上了解一下吧!;-)
SwiftData 平时是怎么存属性的?
对常见属性,SwiftData 会直接为它们创建可持久化、可查询的 schema。
swift
import SwiftData
@Model
final class Book {
var title: String
var pageCount: Int
var publishedAt: Date
init(title: String, pageCount: Int, publishedAt: Date) {
self.title = title
self.pageCount = pageCount
self.publishedAt = publishedAt
}
}
String、常规整数、Date 等类型,SwiftData 都认识。它们像数据库里的常驻嘉宾,不需要 .codable 介绍自己。

简单的 Codable struct 为什么通常不必加?
你拥有的简单值类型,即使是结构体,只要遵守 Codable,通常可以直接作为模型属性。
swift
import SwiftData
@Model
final class Trip {
struct Location: Codable {
var latitude: Double
var longitude: Double
}
var name: String
var location: Location?
init(name: String, location: Location? = nil) {
self.name = name
self.location = location
}
}
这里不应因为 Location 遵守 Codable 就急着写:
swift
// 通常没有必要这样做
@Attribute(.codable)
var location: Location?
原因是:Location 是一个简单、由你控制的值类型。让 SwiftData直接建模,未来更有机会获得查询、排序、索引和迁移方面的能力。
可以把 .codable 想成真空包装:
方便保存,但打开包装前,SwiftData 不知道里面装的是经纬度还是昨晚的披萨。

泛型类型为什么常常需要 .codable?
看起来,Measurement<UnitTemperature>、Measurement<UnitLength> 这类类型都因为带泛型才需要 .codable:
swift
import Foundation
import SwiftData
@Model
final class Experiment {
@Attribute(.codable)
var temperature: Measurement<UnitTemperature>
@Attribute(.codable)
var length: Measurement<UnitLength>
init(
temperature: Measurement<UnitTemperature>,
length: Measurement<UnitLength>
) {
self.temperature = temperature
self.length = length
}
}
但真正原因不是尖括号。
Measurement 是 Foundation 提供的复杂类型;SwiftData 无法可靠地将它拆解为稳定的原生 schema,例如"数值列 + 单位列",并处理其单位体系与内部实现。因此需要 .codable 明确告诉 SwiftData:
不用研究它的内部构造;请调用它的编码和解码能力。
换句话说,泛型只是这类类型常见的外表,不是 SwiftData 拒绝建模的理由。
那么,Int128 为什么也需要 .codable?
这正是最容易让人困惑的地方。
Int128 没有泛型,而且是 Swift 标准库中的整数类型,也遵守 Codable。但它仍然需要这样保存:
swift
import SwiftData
@Model
final class TimingRecord {
@Attribute(.codable)
var duration: Int128
init(duration: Int128) {
self.duration = duration
}
}
原因是,能编码不等于能被 SwiftData 原生映射为数据库列。
SwiftData 原生支持的是一组它知道如何映射、比较、排序和查询的属性类型。Int128 虽然是标量,但宽度为 128 位,超出了 SwiftData 当前原生整数属性映射的范围。于是 .codable 让 SwiftData 把它当作一个可编码的整体保存。
所以,Measurement<UnitTemperature> 和 Int128 的共同点不是"都很复杂"或"都带泛型",而是:
SwiftData 都无法将它们直接视为可原生建模的属性。

.codable 得到了什么,又失去了什么?
.codable 的好处是可以保存原本无法纳入 SwiftData schema 的 Codable 类型,例如系统框架或第三方 SDK 的类型。
WWDC26 官方示例使用了 MKMapItem.Identifier:
swift
import MapKit
import SwiftData
@Model
final class Trip {
var name: String
@Attribute(.codable)
var mapItemIdentifier: MKMapItem.Identifier?
init(
name: String,
mapItemIdentifier: MKMapItem.Identifier? = nil
) {
self.name = name
self.mapItemIdentifier = mapItemIdentifier
}
}
这是一个典型的合适场景:该类型来自 MapKit,你不能修改它,也不能把它改造成自己的 @Model;但它遵守 Codable。
代价则是,SwiftData 会把 .codable 属性视为不透明整体:
swift
// 不要期待 SwiftData 能理解或查询 codable 属性的内部字段
@Attribute(.codable)
var location: Location
你不能依赖其内部内容进行:
#Predicate过滤;SortDescriptor排序;- 建立索引;
- 自动 schema migration;
例如,若要按纬度搜索地点,不能把经纬度藏进 .codable 的 Location 后再指望 SwiftData"透视"它。它不会读心,也不会拆包。

自己的类型:什么时候该用,什么时候不该用?
对于你自己定义的业务模型,优先选择能被 SwiftData理解的普通属性或独立模型。
swift
import SwiftData
@Model
final class WeatherRecord {
var city: String
var temperatureCelsius: Double
var recordedAt: Date
init(city: String, temperatureCelsius: Double, recordedAt: Date) {
self.city = city
self.temperatureCelsius = temperatureCelsius
self.recordedAt = recordedAt
}
}
这种设计很朴素,但很有力量:
swift
let predicate = #Predicate<WeatherRecord> {
$0.temperatureCelsius > 30
}
相反,若把所有内容塞进一个 .codable 结构体,存储会很省心,查询却会很伤心。
对于外部类型、不可修改的 SDK 类型,或者确实无法被 SwiftData 原生建模的值,.codable 才是合适工具。
.codable 类型演进时要格外小心
SwiftData 不会检查 .codable 属性内部结构的变化。新增字段、删除字段或更改字段类型,不会自动产生 SwiftData migration。
因此,你的 Codable 实现要主动兼容旧数据。
swift
struct RemotePayload: Codable {
var source: String
var revision: Int
// 新字段提供默认值,让旧数据仍可被解码
var displayName: String = ""
}
对于更复杂的变更,应自定义 init(from:),为旧版本数据提供明确的回退策略。否则,应用升级后可能不是"数据迁移",而是"数据失忆"。
选择清单
| 你的属性 | 建议 |
|---|---|
String、Bool、Date、Int64 等常规原生类型 |
不加 .codable |
自己定义的简单 Codable struct 或原始值枚举 |
通常不加 .codable |
| 经常要筛选、排序、建立索引的数据 | 拆为原生属性,不要藏进 .codable |
Measurement 等无法由 SwiftData 原生建模的 Foundation 类型 |
加 .codable |
MKMapItem.Identifier 等第三方或系统框架的 Codable 类型 |
加 .codable |
Int128、UInt128 |
加 .codable,或在业务允许时改用 Int64 |

总结
.codable 不是"泛型修饰符",也不是"只要遵守 Codable 就该加"的标记。它是 SwiftData 的一条备用通道:
当一个类型本身会编码和解码,但 SwiftData 不会原生建模时,使用
@Attribute(.codable)。
因此:
- 简单、自有、希望被查询的数据:让 SwiftData 正常理解它。
- 外部、复杂、无法原生映射的数据:交给
.codable。 Int128:不是泛型,但也不在 SwiftData 的原生整数映射范围内,所以同样适用.codable。
最后,感谢大家的观赏!
我是大熊猫侯佩,我们下次不见不散!8-)