2025-01-18 18:47:27
,某些文章具有时效性,若有错误或已失效,请在下方留言。框架默认的行为适用于大多数使用场景,能够有效管理模型类的存储属性。然而,如果您需要更改特定属性的持久化行为,可以使用 @Attribute
宏对其进行注解。例如,您可以通过指定某个属性的值在该模型的所有实例中唯一,从而避免模型数据的冲突。
@Model class RemoteImage {
@Attribute(.unique) var sourceURL: URL
var data: Data
init(sourceURL: URL, data: Data = Data()) {
self.sourceURL = sourceURL
self.data = data
}
}
存储在外部文件中
SwiftData 适合存储字符串
、整数
、日期
和 Codable
对象,但对于较大的数据(如图像
或视频
),最好将其存储在外部文件中,并在 SwiftData 对象中引用文件名。
使用.externalStorage
属性,SwiftData 可以将数据存储在主存储之外,仅保存外部文件名,并自动连接二者,外部数据的行为与内部数据类似。
例如,存储游戏玩家头像的代码如下:
@Model class User {
var name: String
var score: Int
@Attribute(.externalStorage) var avatar: Data
init(name: String, score: Int, avatar: Data) {
self.name = name
self.score = score
self.avatar = avatar
}
}
可以将其与其他属性组合,如加密:
@Attribute(.externalStorage, .allowsCloudEncryption) var avatar: Data
1. externalStorage
属性只是建议,SwiftData 可能仍会在主存储中保存小于约 128K 的 Data
对象。
2. 使用 String
类型时,SwiftData 可无限制存储字符串,不必使用外部文件。
3. 数据的存储位置对我们来说并不重要,但不能在谓词中使用外部存储的属性,因为外部文件对底层数据不可见。遇到相关错误时,请注意可能的陷阱。
@Attribute 参数
options
:应用于附加属性的一系列选项,用于自定义其行为。有关可能的值,请参见Schema.Attribute.Option
。originalName
:属性的先前名称,如果与当前模式版本中的名称不同。默认值为nil
。hashModifier
:表示附加属性最新版本的唯一哈希值。默认值为nil
。
属性选项 | 描述 |
---|---|
allowsCloudEncryption | 将属性的值以加密形式存储。 |
externalStorage | 将属性的值作为二进制数据存储在模型存储旁边。 |
preserveValueOnDeletion | 当上下文删除拥有模型时,保留属性的值在持久化历史中。 |
spotlight | 将属性的值进行索引,以便出现在 Spotlight 搜索结果中。 |
unique | 确保属性的值在相同类型的所有模型中是唯一的。 |
transformable(by: ValueTransformer.Type) | 在内存形式和持久化形式之间转换属性的值。 |
transformable(by: String) |
创建派生属性
在 Swift 中,派生属性可以自动更新以反映模型中的更改。以下代码展示了如何创建一个 Player
类,其中包含一个lastModified
属性,以及一个调整任意值的 update()
方法,这个方法同时会将 lastModified
更新为当前日期和时间。
@Model class Player {
var name: String
var score: Int
var lastModified: Date
init(name: String, score: Int) {
self.name = name
self.score = score
self.lastModified = .now
}
func update<T>(keyPath: ReferenceWritableKeyPath<Player, T>, to value: T) {
self[keyPath: keyPath] = value
lastModified = .now
}
}
有了这个实现,我们可以使用 item.update(keyPath:\.score, to:10)
来更改 score
属性, 同时更新 lastModified
。
SwiftData 属性不支持 willSet
或 didset
属性观察者,因此我们实际上是通过一个新方法来传递我们的更改,以代替使用 didSet
。
下面示例演示如何在 SwiftUl 中使用这个 Player
类。在这个示例中,当更新 score
属性时, lastModified
字段也会被相应地更新:
struct ContentView: View {
@Environment(\.modelContext) private var context
@Query private var players: [Player]
var body: some View {
NavigationSplitView {
List {
ForEach(players) { item in
NavigationLink {
Text("Item at \(item.lastModified, format: Date.FormatStyle(date: .numeric, time: .standard))")
Text("\(item.score)")
Button(action: updateItem) {
Label("Update Item", systemImage: "pencil.line")
}
} label: {
Text(item.lastModified, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
.onDelete(perform: deleteItems)
}
} detail: {
Text("Select an item")
}
}
private func updateItem() {
withAnimation {
if let item = players.first {
item.update(keyPath: \.score, to: 10)
}
}
}
}
通过这种方式,我们不仅可以有效管理模型属性的更新,还能确保相关的lastModified
属性始终保持最新。
派生属性另外一个技巧
下面示例更新 students
数据,会自动更新 studentCount
这是一个变通的技巧
@Model class School {
var students: [Student]
var studentCount: Int {
students.count
}
init(students: [Student]) {
self.students = students
}
}
@Model class Student {
var name: String
init(name: String) {
self.name = name
}
}
加密 SwiftData
如果您存储的信息特别敏感,可以采取一到两步措施,以确保数据的安全存储。最简单且确保有效的选项是对任何应在iCloud 中以加密形式存储的属性使用 .allowsCloudEncryption
属性,示例如下:
@Attribute(.allowsCloudEncryption) var cardNumber: Int
顾名思义,这种加密仅在iCloud 中有效;此属性对本地存储的数据没有影响。
暂无评论内容