Ask Apple 为开发者与苹果工程师创造了在 WWDC 之外进行直接交流的时机。本文对本次活动中与 Core Data 有关的一些问答进行了整理,并增加了一点个人见解。本文为下篇。

原文发表在我的博客wwww.fatbobman.com

欢迎订阅我的公共号:【肘子的Swift记事本】

Q&A

派生特点( Derived Attributes )

Q:嗨,能否共享除 .@count 之外的“派生特点”的更多语法示例,提早致谢。

A:NSDerivedAttributeDescription 的文档中有一些 阐明 。

派生特点的值是从一个或多个其他的特点的值派生而来。浅显地说,就是在创立或修改保管目标实例时,Core Data 将主动为派生特点生成值。值依据预设的派生表达式( Derived Expression )并经过其他的特点值核算而来。详细内容请参阅 如安在 Core Data 中运用 Derived 和 Transient 特点 一文。

主程序与扩展程序数据同步

Q:我有一个主应用程序和一个扩展程序,它们都读取相同的 Core Data 数据库。但是,当我在主应用程序中进行更改时,我的扩展程序在从头发动之前不会看到更改。我是经过简略地调用 NSManagedObjectContext.refreshAllObjects 来处理这个问题,仍是必须用较困难的办法 —— 启用前史盯梢、检测远程更改、合并来自业务的更改、整理业务前史?

A:你应该运用 NSPersistentStore 上的 NSPersistentStoreRemoteChangeNotificationOptionKey 选项启用远程更改告知这一办法。该办法的 Persistent History 部分有助于保证你不会大量重复地从数据库中获取数据,而且仅在你需求的数据发生更改时才改写。

又是一个有关耐久化前史盯梢的问题。苹果然应该为该功用供给一个愈加清晰的文档。运用 Persistent History Tracking Kit 能够减少你的开发工作量。

怎么更新经过文件系统删去的 Core Data 数据的 Spotlight 索引

Q:在运用 Spotlight 索引 Core Data 中的内容时,是否能够指定 Spotlight 索引的存储方位?我有一个基于文档的应用程序( document based app ),一些文件以及 Core Data 创立的 sqlite 文件被制形成了一个包( package bundle )。假如用户在应用程序之外删去文档,例如在 Finder 中,我期望 Spotlight 中的索引与它一同被删去。所以我想假如索引能够存储在包文件夹中,那就能够处理这种状况。有没有办法正确处理这种状况?

A:听起来这是一个有价值的功用建议,鼓舞你提交反馈请求!当时,从应用程序中调用 API 是从索引中删去项目的仅有办法。

当时 Spotlight 的确无法处理类似的状况。假如用户经过文件系统删去了这些文档( 不经过应用程序 ),那么除非应用程序能够了解哪个文档被删去了,然后经过 CSSearchableIndex.default().deleteSearchableItems(withDomainIdentifiers:) 删去归于该文档的索引,否则只能等候这些索引到期后主动从 Spotlight 中消失。参阅 在 Spotlight 中展现应用中的 Core Data 数据 了解更多内容。

@FetchRequest 的功用怎么

Q:@FetchRequest 在功用方面是否优于在 ViewModel 的结构办法中经过 fetchRequest 获取数据的办法?

A:在初始数据获取完结后,@FetchRequest 的成本与成果改动的多少有关,而手动从头获取的成本与成果的总数有关。@FetchRequest 包装了一个 NSFetchedResultsController,它没有自己的特别逻辑。

获取数据的办法

Q:我想知道哪种是比较好的办法?

  1. 在应用程序中一次性加载 CoreData 数据并将其保存在局部变量
  2. 运用多个 FetchRequests

我现在在 SwiftUI 中运用 UICalendarView 并从 CoreData 中获取数据。能够在 calendarView(_:decorationFor:) 办法中经过 fetchRequest 来为日历中的每个日期加载数据吗( 应该是指第二种办法 )?仍是只运用一个 fetchRequest,然后将数据保存在本地,并经过上述办法访问它( 应该是指第一种办法 )?我想知道这里的最佳做法是什么。谢谢!

A:一般来说,不同的视图常常运用不同的获取请求。关于日期范围之类的内容,你或许期望一次获取一批。过长的 I/O 会使您的视图绘图阻滞。太短的 I/O 会导致你宣布太多的独自请求,这会大大下降功率。 Instruments 的 Core Data 功用东西能够协助查询什么才是最适合您的计划。

UICalendarView 是 iOS 16 新增的控件,MultiDatePicker 只完成了它的部分功用。UICalendarView 答应开发者为特定日期增加装饰,运用办法能够参阅 Getting UIKit’s UICalendarView from iOS 16 fully functioning in a SwiftUI app 一文。

检索 NSAttributedString

Q:我需求将 NSAttributedString 存储在数据库中,而且能够对特点字符串中的任何文本进行搜索。经过创立两个独自的特点,一个包括纯文本字符串,另一个包括特点字符串的 Transformable 数据是否为最好的办法?是否有另一种更好的办法能够不经过两个特点来减少存储的数据量?

A:你运用的正是当时推荐的办法。此外,纯文本特点能够被 Spotlight 索引,方便它们被系统搜索。

生成对应数据的纯文本以进行检索,是一种很常见的办法。在某些状况下,即便特点的原始内容为纯文本,也能够经过为其生成标准化版别( 疏忽大小写以及变音符号的版别 )以提高检索功率。

私有上下文

Q:怎么配置 Core Data Stack,以便在后台保存更改时,用户能够持续运用应用程序。

A:NSPersistentContainer 能够满足你的需求,你能够运用 viewContext 来驱动与用户交互的 UI,同时经过 newBackgroundContext 办法创立私有上下文,并在其上完结数据的保存。请保证在 viewContext 上开启主动合并更改,以便 backgroundContext 上的更改能够在 viewContext 中主动更新。

无论是经过 newBackgroundContext 显式地创立一个私有上下文,仍是经过 performBackgroundTask 在一个临时私有上下文中进行操作,都不能在私有上下文中运用从 viewContext 中获取到的保管目标。保管目标是线程绑定的。即便都来自于私有上下文但分归于不同的上下文,它也只能在其对应的上下文中运用。

怎么从 UserDefaults 转化至 Core Data

Q:现在,我的应用程序运用 @AppStorage 进行数据耐久化。我有三个首要的模型目标,它们被存储在当时设备上。我想切换成 Core Data + CloudKit 的办法。当现有用户翻开新应用程序时,怎么保证现有的本地 @AppStorage 数据被安全地转化到 Core Data + CloudKit 中?

A:发动时检测 UserDefaults 是否为空,假如不是,则导入 Core Data,然后删去本地的 UserDefaults。

异步保存

Q:嗨,将相片数据保存到 Core Data 时运用异步是否有必要?谢谢!

A:你是在问是否应该运用 perform 或 performAndWait?我认为这取决于你的要求和所需的 UX 体验。

perform 和 performAndWait 别离对应的是在上下文中进行异步/同步操作。关于私有上下文,即便运用 performAndWait 一般也不会对 UI 形成影响。

数据模型源文件( Class/Category/Manual )

Q:我期望取得与 Core Data 模型实体生成( Codegen )种类有关的辅导。例如,什么状况下应该运用手动?我也不确认 Category/Extension 的效果以及如安在它和 Class 之间进行选择?

A:大多数人会运用 Class,并在他们自己的保管目标扩展中增加他们需求的任何自界说办法。但是在极少数状况下,例如你需求增加必须在类界说中声明的特点,此时应运用 Category/Extension 使你能够控制所需的类声明。

在前期的 Xcode 版别中,运用 Class 形式会生成两个文件,xxx+CoreDataClass.swift 和 xxx+CoreDataProperties.swift 。xxx+CoreDataProperties.swift 中是经过扩展为 Entity 的特点创立的声明,xxx+CoreDataClass.swift 是类的界说。而 Category/Extension 形式只会生成 xxx+CoreDataProperties.swift ,也就是说用户需求自己来写类的界说。不过在新版的 Xcode( 至少从版别 13 起 )中,两者之间现已没有区别了。都会生成两个文件,而且假如用户在类的界说中增加了自界说特点,Xcode 也不会在从头生成的代码中对其进行覆盖。当生成文件后,需求将 Entity 切换成 Manual /None 形式,否则 Xcode 会呈现类型重复声明的错误( Xcode 中还会有另一份 Entity 界说保存在项目内部 ),假如仍无法编译,应清空编译缓存。

经过 CloudKit Dashboard 删去数据

Q:一个与 Core Data 与 CloudKit 同步的问题。我注意到,当我运用 Safari 客户端从 CloudKit 数据库中删去一条记载时( 经过 CloudKit Dashboard ),该目标仍会保留在设备上的 Core Data 数据存储中。这是有意为之的吗?如安在 CloudKit 办理器与设备之间同步这些更改?谢谢!

A:尚不清楚此工作流程是否会向 NSPersistentCloudKitContainer 生成推送告知。假如你从头发动应用程序,应该会看到更改。

怎么确认是否已同步完结

Q:我正在运用 NSPersistentCloudKitContainer,并想改善设备初度从 iCloud 上下载数据时的用户体验。有没有办法告知用户数据已完结同步?我知道 NSPersistentCloudKitContainer.eventChangedNotification,但它好像没有真实的办法来告知应用同步何时完结。

A:其他设备总是或许做出无穷无尽的新改动,你能做的是检查哪些导入已发动及其完结状况。欢迎向咱们提交功用需求的 FB。( The theoretical is intractable, the other devices can always be making an infinite stream of new changes, Seeing which imports have been kicked off and their completion status is the best you can do, But you could file a feedback for an enhancement for an approximate answer )

苹果的工程师没有对此进行正面答复。有关同步进度的问题,无论是 WWDC、开发者论坛仍是在本次 Ask Apple 上都被多次提及,但直到现在,尚没有好的处理计划。我的建议是,在应用中( 尤其是初次发动时 ),在同步处于 import 状况时( 经过 eventChangedNotification 取得 )应对用户给予提示( 运用 ProgressView 之类的动态元素 )。Core Data with CloudKit 的同步机制会将同步过程分多次进行。也就是说,关于初次同步来说,import 状况很或许会多次呈现( 无法经过 import 状况发生转变来判别导入结束 )。经过导入状况提示,能够在必定程度上减轻用户的疑惑。另外能够考虑运用 CloudKit API 查询云端的数据条数,然后与现已同步到本地的记载数进行比对,取得大致的同步进度( 此办法仅适用于数据模型简略,联系不太杂乱的状况 )。

实体特点的可选性

Q:Core Data 中实体特点的可选性表现与预期不一致。假如我将某个特点标记为可选,则该特点不应具有默认值,而且保管特点应一直为可选特点。假如我将其标记为非可选,则它应该需求默认值,而且保管特点应一直是非可选的。咱们是否能够期待将来( 至少在新项目中 )做出这样的批改?

A:Core Data 的可选性理念早于 Swift 的存在,答应特点暂时无效。例如,当你创立一个带有字符串特点的新目标时,初始值( 在没有默认值的状况下 )是 nil,这在目标被验证之前( 一般在 save 时 )是没有问题的。在可选标量的状况下,Core Data 受限于 Objective-C 中可表达的类型限制( 例如没有 Int64 这样的类型,可选的类型只能表达为 NSNumber )。就这一想法提交反馈陈述或许是你最好的选择。

实体特点的可选性关于 Core Data 的初学者来说是一个容易困惑的当地。即便你在模型编辑器中将特点( 例如字符串 )标记为非可选( 设定了默认值 ),但在从保管目标获取特点值的时分,返回值仍会是 Optional<String> 类型。关于上面的问题,能够考虑如下的处理办法:1、关于某些类型的特点来说,能够经过手动界说( 或修改 Xcode 生成的 subclass 源文件 ),将生成代码中的类型 String? 改成 String;2、声明一个非可选值的核算特点,并在其间对可选值特点值进行处理;3、将保管目标实例整体转化成对 SwiftUI 视图愈加友好的值类型。

数据手动排序

Q:在我的应用程序中,用户能够在表视图中经过拖放来从头排列项目。我的数据模型中有一个 Int16 类型的 userOrder 特点,在表视图的行被从头排序后,有什么好的办法来保存数据的新次序?

A:与其运用 userorder == 0 存储第一个目标,运用 userOrder == 1 存储第二个目标,运用 userOrder == 2 存储第三个目标,或许将其建模为一种有序的联系( ordered )是更好的选择。让 Core Data 为你做这件事。为了办理有序的联系,Core Data 在 UInt16 空间中核算一个目标的索引,正好在前一个和后一个目标的中间。当整数空间用完时,将在任何一个方向上跨出一个目标,并均匀地从头分配这些目标。

很惋惜,有序联系无法在开启 Core Data 云同步的状况下运用,在此种状况下,发问者当时的做法应该是正确的选择。

筛选联系数据

Q:我发现在 SwiftUI 中运用 @FetchRequest 是将用户界面与 Core Data 数据绑定很好的手段。但是,在运用联系来取得相同的无缝绑守时,我碰到了一个小问题。由于 NSManagedObjects 以 NSSet 的形式表明一对多的联系,我必须在它自己的 @FetchRequest 中从头获取 “子女”( 多方的数据 ),然后失去 Core Data 联系特点的优点。我的办法有什么问题?

A:这听起来与另一个问题类似,我在这个问题中建议运用谓词来过滤只具有某种联系的目标。我想相同的办法应该对你有用?让 Core Data 经过构建一个谓词来完结过滤工作会更快,比如 NSPredicate(format: "country = %@", country)

NSManagedObject 契合 ObservableObject 协议,这意味着当它的特点值发生改动时将会经过 Publisher 告知订阅者。惋惜的是,可监控的改动中并不包括联系目标中的特点值改动。经过谓词从头获取联系目标列表或许是现在最好的办法。另外,Antoine van der Lee 曾写过一篇经过扩展 NSFetchedResultsController 来完成监控联系目标特点改动的文章 NSFetchedResultsController extension to observe relationship changes 。

在耐久化前史中怎么表现有序目标的改动状况

Q:耐久化前史中是怎么表现 “有序” 联系中的目标的次序发生了改动?NSPersistentHistoryChange 是否包括父实体或子实体?updatedProperties 中有哪些特点?

A:关于排序的改动,联系的两头都会显示为 NSPersistentHistoryChange,并在 updatedProperties 中列出联系。

经过 navigationDestination 传递保管目标的需求

Q:我有一个与 SwiftUI 的 navigationDestination(for: myCoreDataClass) 有关的问题,需求让我的 NSManagedObjects 中契合 Codable 协议( 猜想是想对 Path 进行耐久化 )。我手动生成了 NSManagedObject 代码并完成了 Codable 协议来完成这一目标。有什么更好的处理办法吗?谢谢。

A:Codable 无法准确地对目标图中的目标进行独自编码。相反,你应该创立一个适合于此处需求的数据子集的可编码转化。或许能够运用 URIRepresentation 。

当 NSManagedObject 包括联系时,对其进行编码是极为困难的。navigationDestination 对传入数据的仅有要求是契合 Hashable 协议,因此传入保管目标 ID 对应的 URL 应该是最佳的选择( 经过 objectID.uriRepresentation,URL 契合 Codable 协议,满足对 Path 进行耐久化的需求 )。

总结

在上下两篇问答汇总中,我疏忽掉了没有取得结论的问题。期望上述的整理能够对你有所协助。

欢迎经过 Twitter、 Discord 频道 或博客的留言板与我进行交流。

订阅下方的 邮件列表,能够及时取得每周的 Tips 汇总。

原文发表在我的博客wwww.fatbobman.com

欢迎订阅我的公共号:【肘子的Swift记事本】