SwiftData 改进了创建数据模型的机制,纳入了基于模型代码创建谓词的类型安全模式。因此,开发人员在为 SwiftData 构造谓词时会遇到大量涉及可选值的操作。本文将探讨在构建谓词时处理可选值的一些技术和注意事项。

从“由内而外”到“由外而内”的转变

在 SwiftData 的众多创新中,最引人注目的是允许开发人员直接通过代码声明数据模型。在Core Data中,开发者必须先在Xcode的模型编辑器中创建一个数据模型(对应NSManagedObjectModel),然后才能编写或自动生成NSManagedObject子类代码。

此过程本质上是从模型(“内部”)转换为类型代码(“外部”)。开发者可以对类型代码进行一定程度的调整,例如将 Optional 更改为 Non-OptionalNSSetSet,优化开发体验,前提是这些修改不影响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 elseif每个结构都被视为单个表达式,每个结构都与 PredicateExpressions 直接对应。相反, if 结构之外的附加返回对应于两个不同的表达式。

虽然谓词闭包中只能包含一个表达式,但仍然可以通过嵌套构建复杂的查询逻辑。

方法3:使用flatMap方法

flatMap方法可以处理可选值,当不nil,结果仍然能够使用 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 == "根"
}

此规则也适用于对象之间可选关系属性的比较。例如,在 ItemNote 之间的一对一可选关系中,可以直接比较(即使name也是可选类型):

迅速

 

让谓词 = #Predicate {
 $0.note?.name == "root"
}

具有可选链接的特殊情况

虽然当可选链仅包含一个 ? 时,在相等比较中不需要特殊处理,但涉及多个 ?在链中,即使代码编译运行没有错误,SwiftData也无法通过这样的谓词从数据库检索到正确的结果。

考虑以下场景,其中 Item< 之间存在一对一的可选关系code>Note,以及 NoteParent

迅速

 

让谓词 = #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 的强大功能。

Comments are closed.