Posted by Socrates on | Featured
SwiftData 改进了创建数据模型的机制,纳入了基于模型代码创建谓词的类型安全模式。因此,开发人员在为 SwiftData 构造谓词时会遇到大量涉及可选值的操作。本文将探讨在构建谓词时处理可选值的一些技术和注意事项。
从“由内而外”到“由外而内”的转变
在 SwiftData 的众多创新中,最引人注目的是允许开发人员直接通过代码声明数据模型。在Core Data中,开发者必须先在Xcode的模型编辑器中创建一个数据模型(对应NSManagedObjectModel),然后才能编写或自动生成NSManagedObject子类代码。
此过程本质上是从模型(“内部”)转换为类型代码(“外部”)。开发者可以对类型代码进行一定程度的调整,例如将 Optional
更改为 Non-Optional code> 或 NSSet
为 Set
,优化开发体验,前提是这些修改不影响Core Data代码和模型之间的映射。
SwiftData的纯代码声明方式彻底改变了这个过程。在SwiftData中,类型代码和数据模型的声明是同时进行的,或者更准确地说,SwiftData根据开发者声明的类型代码自动生成相应的数据模型。申报方式由传统的“由内而外”转变为“由外而内”。
可选值和谓词
在为 Core Data 创建谓词的过程中,谓词表达式与类型代码没有直接链接。这些表达式中使用的属性与模型编辑器(数据模型)中定义的属性相对应,它们的“可选”特征与 Swift 中可选类型的概念不一致,而是指示 SQLite 字段是否可以 NULL
。这意味着当谓词表达式涉及可以为 NULL 和非 NULL 值的属性时,通常不需要考虑其可选性。
公共类注意:NSManagedObject {
@NSManaged 公共变量名称:字符串?
}
let predicate = NSPredicate(format: "name BEGINSWITH %@", "fat")
然而,SwiftData 的出现改变了这种情况。由于 SwiftData 谓词的构造是基于模型代码的,因此其中的可选类型真正体现了 Swift 中可选的概念。这需要在构建谓词时特别注意可选值的处理。
考虑以下 SwiftData 代码示例,其中对可选值的不当处理将导致编译错误:
}
// 可选类型“String?”的值必须展开以引用包装基类型“String”的成员“starts”
让 predicate2 = #Predicate { 注意
note.name?.starts(with: "fat") // 错误
}
// 无法转换“Bool”类型的值?闭包结果类型 'Bool'" data-lang="text/x-swift">
@Model
最后一堂课注意{
变量名称:字符串?
初始化(名称:字符串?){
self.name = 名字
}
}
让 predicate1 = #Predicate { 注意
note.name.starts(with: "fat") // 错误
}
// 可选类型“String?”的值必须展开以引用包装基类型“String”的成员“starts”
让 predicate2 = #Predicate { 注意
note.name?.starts(with: "fat") // 错误
}
// 无法转换“Bool”类型的值?闭包结果类型“Bool”
因此,在为 SwiftData 构造谓词时,正确处理可选值成为一个关键的考虑因素。
正确处理 SwiftData 中的可选值
尽管 SwiftData 中的谓词构造类似于编写返回布尔值的闭包,但开发人员只能使用 官方文档,通过宏转换为对应的PredicateExpressions
。对于上面提到的可选类型 name
属性,开发者可以使用以下方法处理:
方法 1:使用可选链接和零合并运算符
通过将可选链接 (?.
) 与零合并运算符 (??
),当属性为 nil
时,您可以提供默认布尔值。
让 predicate1 = #Predicate {
$0.name?.starts(with: "fat") ??错误的
}前>
方法2:使用可选绑定
通过可选绑定(iflet
),可以在属性不为nil时执行特定的逻辑,或者返回false
否则。
让 predicate2 = #Predicate {
如果让 name = $0.name {
返回 name.starts(with: "fat")
} 别的 {
返回错误
}
}
请注意,谓词主体只能包含单个表达式。因此,尝试返回 if
之外的另一个值将不会构造有效的谓词:
让 predicate2 = #Predicate {
如果让 name = $0.name {
返回 name.starts(with: "fat")
}
返回错误
}
这里的限制是指 if else
和 if
每个结构都被视为单个表达式,每个结构都与 PredicateExpressions
直接对应。相反, if
结构之外的附加返回对应于两个不同的表达式。
虽然谓词闭包中只能包含一个表达式,但仍然可以通过嵌套构建复杂的查询逻辑。
方法3:使用flatMap
方法
flatMap
方法可以处理可选值,当不nil时应用给定的闭包code>,结果仍然能够使用 nil-coalescing 运算符提供默认值。
让 predicate3 = #Predicate {
$0.name.flatMap { $0.starts(with: "fat") } ??错误的
}
<块引用>
上述策略提供了安全有效的方法来正确处理 SwiftData 谓词构造中的可选值,从而避免编译或运行时错误,确保数据查询的准确性和稳定性。
块引用>
不正确的方法:使用强制展开
即使开发人员确定某个属性不为零,使用 !
在 SwiftData 谓词中强制展开仍然可能导致运行时错误。< /p>
let predicate = #Predicate {
$0.name!.starts(with: "fat") // 错误
}
// 运行时错误:SwiftData.SwiftDataError._Error.unsupportedPredicate
处理特殊情况下的可选值
在 SwiftData 中构造谓词时,虽然通常需要特定方法来处理可选值,但在某些特殊情况下,规则略有不同。
直接相等比较
SwiftData 允许直接比较涉及可选值的相等 (==
) 操作,而无需额外处理可选性。这意味着即使一个属性是可选类型,也可以直接进行比较,如下所示:
let predicate = #Predicate {
$0.name == "根"
}
此规则也适用于对象之间可选关系属性的比较。例如,在 Item
和 Note
之间的一对一可选关系中,可以直接比较(即使name
也是可选类型):
让谓词 = #Predicate- {
$0.note?.name == "root"
}
具有可选链接的特殊情况
虽然当可选链仅包含一个 ?
时,在相等比较中不需要特殊处理,但涉及多个 ?
在链中,即使代码编译运行没有错误,SwiftData也无法通过这样的谓词从数据库检索到正确的结果。
考虑以下场景,其中 Item
和 < 之间存在一对一的可选关系code>Note,以及 Note
和 Parent 之间代码>:
让谓词 = #Predicate- {
$0.note?.parent?.persistentModelID == rootNoteID
}
要解决这个问题,需要保证可选链中只包含一个?
。这可以通过部分展开可选链来实现,例如:
让谓词 = #Predicate- {
如果让 note = $0.note {
返回 note.parent?.persistentModelID == rootNoteID
} 别的 {
返回错误
}
}
或者:
让谓词 = #Predicate- {
如果让note = $0.note,让parent = note.parent {
返回parent.persistentModelID == rootNoteID
} 别的 {
返回错误
}
}
结论
在本文中,我们探讨了如何在 SwiftData 中构造谓词的过程中正确处理可选值。通过介绍各种方法,包括使用可选链接和 nil-coalescing 运算符、可选绑定和 flatMap
方法,我们提供了以下策略:有效处理选择性。此外,我们还重点介绍了可选值直接相等比较的特殊情况,以及可选链包含多个 ?
时所需的特殊处理。这些技巧和注意事项旨在帮助开发人员避免常见陷阱,确保构建准确高效的数据查询谓词,从而充分利用 SwiftData 的强大功能。