iOS面试问题第5部分:核心数据📗

是时候挖掘核心数据了。 如果您错过了上一个第4部分:UIKit,请检查一下。 现在开始吧!!! 📀

问:持久性存储协调器可以有多个持久性存储吗?

持久性存储协调器将持久性对象存储和托管对象模型相关联 ,并向托管对象上下文提供外观,以使一组持久性存储显示为单个聚合存储。 它具有对托管对象模型的引用,该对象模型描述了商店中或其管理的商店中的实体。

在许多应用程序中,您只有一个商店,但是在复杂的应用程序中,可能有多个商店,每个商店可能包含不同的实体。

问:为什么我们需要多个持久性存储?

  • 应用程序具有固定数据集,该数据集已包含在捆绑软件中
  • 应用程序处理的某些数据是我们不希望保留在磁盘上的敏感信息
  • 对不同实体有不同的存储要求

您可能已通过将文件包中的文件复制到可写位置并将其用作整个数据存储库的方式来满足第一个要求。 您可能已经通过手动删除对象来处理第二个问题。 在两种情况下,单独的持久性存储都是更好的解决方案。 每个持久性存储都有自己的特征-可以是只读的,可以二进制或SQLite或内存形式存储(在OS X上,也可以使用XML后备存储),也可以是您自己的NSIncrementalStore实现。 可以将模型的不同部分存储在不同的持久性存储中,以利用这种灵活性。

问:如何添加多个永久存储?

借助托管对象模型中的配置,我们可以实现这一目标。 我们可以创建多个配置并将其分配给不同的持久性存储。

我们希望每个商店一个配置 ,并且每个实体应仅添加到一个配置 (除了默认配置)。 实体可以具有多种配置,但是在这种情况下,您必须手动将每个对象分配给存储。

通过为每个商店创建一个配置并将每个实体分配给一个配置,我们使核心数据框架能够将实体定向到不同的商店,而无需任何进一步的交互。

问:我们可以在不同的持久存储实体之间建立关系吗?

您必须注意不要创建从一个持久性存储中的实例到另一个持久性存储中的实例的关系,因为Core Data不支持这种关系。 如果需要在不同商店中的实体之间创建关系,则通常使用获取的属性。

问:一个持久性存储协调器可以有多少个托管对象模型?

每个模型我们只能有一个持久性存储协调器。

问:持久性存储的类型?

问:持久存储安全性有哪些限制?

Apple Docs说:Core Data 不保证不受信任来源(与内部生成的存储相对) 的持久存储的安全性,并且无法检测文件是否已被恶意修改SQLite存储提供的安全性比XML和二进制存储好一些,但是不应将其固有地视为安全的 。 还应注意,元数据中存储的数据可能会独立于存储数据而被篡改。 为确保数据安全,请使用加密磁盘映像之类的技术

问:核心数据中的并发类型是什么?

两种并发模式NSMainQueueConcurrencyTypeNSPrivateQueueConcurrencyType

NSMainQueueConcurrencyType特别用于您的应用程序界面,并且只能在应用程序的主队列上使用。

NSPrivateQueueConcurrencyType配置在初始化时创建自己的队列,并且只能在该队列上使用。 因为该队列是私有的,并且在NSManagedObjectContext实例的内部,所以只能通过performBlock:performBlockAndWait:方法对其进行访问。

当您使用NSPersistentContainer ,viewContext属性配置为NSMainQueueConcurrencyType上下文,而与performBackgroundTask:newBackgroundContext关联的上下文配置为NSPrivateQueueConcurrencyType

问:CoreData是否等于SQLite或某些包装器?

不,Core Data是用于管理对象图的框架。 SQLite是一个关系数据库。 SQLite是一个数据库,而Core Data不是。 核心数据可以使用SQLite作为其持久存储,但是框架本身不是数据库。

问:如何在不同队列的托管对象上下文之间传递管理对象?

我们无法传递它们,因为受管对象实例不是线程安全的,也不打算在队列之间传递。 这样做可能会导致数据损坏和应用程序终止。 当有必要将托管对象引用从一个队列NSManagedObjectID给另一个队列时,必须通过NSManagedObjectID实例来完成。 您可以通过在NSManagedObject实例上调用objectID方法来检索托管对象的托管对象ID。

问:核心数据在多线程中的表现如何?

Core Data期望在单个线程上运行。 在多线程应用程序中访问核心数据的一些基本规则:

  • NSManagedObject 实例绝不能从一个线程传递到另一个线程。 如果需要将托管对象从一个线程传递到另一个线程,请使用托管对象的objectID属性。
  • 永远不要在线程之间共享托管对象上下文 。 这是一条硬规则,您不应该打破。
  • 即使NSPersistentStoreCoordinator类也不是线程安全的,但是即使多个托管对象上下文请求访问,该类也知道如何锁定自身,即使这些托管对象上下文在不同线程上运行并运行也是如此。

问:什么是延迟加载,这与核心数据有何关系? 在什么情况下可以派上用场?

当执行提取时,Core Data仅提取您指定的实体的实例。 在某些情况下(请参见故障限制对象图的大小),关系的目的地由故障表示。 访问故障中的数据时,Core Data会自动解决(引发)故障。 相关对象的这种延迟加载对于内存使用而言要好得多,而对于与很少使用(或非常大)的对象相关的对象的取回则要快得多。 但是,这也可能导致Core Data对多个单个对象执行单独的提取请求的情况,这会产生较高的开销。 例如,考虑以下模型:

您可能会获取许多雇员,然后依次询问每个雇员的部门名称,如以下代码片段所示。

 让employeeFetch = NSFetchRequest (实体名称:“雇员”) 
做{
让fetchedEmployees =试试moc.executeFetchRequest(employeeFetch)
对于fetchedEmployees {
打印(“ \(employee.name)-> \(employee.department!.name)”)
}
} {
fatalError(“无法提取员工:\(错误)”)
}

此代码可能导致以下行为:

 杰克->销售[故障触发] 
吉尔->市场营销[故障引发]
Benjy->销售
阿娇->销售
赫克托->工程[故障大火]
米歇尔->市场营销

在这里,有四个往返于持久性存储的往返行程(一个用于原始获取雇员,另一个用于单个部门)。 这些行程代表了至少两次到持久性存储的行程之外的可观开销。

您可以使用两种技术来减轻这种影响: 批处理故障预取

资料来源:Apple文件。

问:如何仅读取实体的一些属性?

这可以通过Core Data的propertiesToFetch实例属性来实现。

问:如何获取不同的值?

有时,您不想获取实际的托管对象。 相反,您只想检索(例如)特定属性的最大值或最小值,或检索给定属性的不同值。 在iOS上,可以使用NSExpressionDescription对象为获取请求指定函数,并可以使用setReturnsDistinctResults:返回唯一值。

苹果文档。

问:什么是fetchedResultController?

用于管理核心数据获取请求的结果并向用户显示数据的控制器。

苹果文件

问:如何同步上下文?

您的托管对象上下文对象应侦听NSManagedObjectContextDidSaveNotification ,以便在任何上下文执行保存时得到通知。 发生这种情况时,您需要每个托管对象上下文调用带有保存保存的通知的mergeChangesFromContextDidSaveNotification: 这会将所有保存的更改从一个MOC合并到其他所有MOC。

看一下这个。

问:什么是NSFetchRequest?

NSFetchRequest是负责从Core Data进行提取的类。 提取请求既强大又灵活。 您可以使用获取请求来获取一组满足提供的条件,单个值等的对象。

问:解释NSPersistentContainer吗?

持久性容器创建并返回了一个容器,该容器已将应用程序的商店加载到其中。 该属性是可选的,因为存在合法的错误条件,可能会导致存储创建失败。

问:解释NSFetchedResultsController吗?

NSFetchedResultsController是一个控制器,但不是视图控制器。 它没有用户界面。 它的目的是通过抽象化使表视图与Core Data支持的数据源同步所需的许多代码,使开发人员的工作更加轻松。

正确设置一个NSFetchedResultsController,您的表将模仿其数据源,而无需编写多行代码。

问:如何在核心数据中存储其他数据类型?

您可以通过使Core Data模型实体中的各个属性具有可转换属性来对其进行加密然后创建一个NSValueTransformer子类,该子类将对该属性的数据进行加密和解密。 虽然这不是您要查找的整个数据库解密,但是与将整个数据库解密到内存相比,它的内存占用量要低得多。 此外,它允许延迟进行解密,而不是全部进行,因此您的应用程序加载速度会更快。 根据所使用的加密,我什至希望用于加载每个实体的磁盘上数据访问的速度将比属性的解密过程慢,因此在访问属性时不会看到那么多的性能损失。

这样的可转换属性非常易于使用,因为您可以像往常一样对它们进行读写,而加密/解密则在后台进行。

问:什么是SQLite限制?

  • 我们需要定义表之间的关系。 定义所有表的架构。
  • 我们必须手动编写查询以获取数据。
  • 我们需要查询结果,然后将其映射到模型。
  • 查询非常快。

问:什么是境界利益?

  • 开源数据库框架。
  • 从头开始实施。
  • 零复制对象存储。
  • 快速。

问:什么是核心数据中的删除规则?

关系的删除规则指定了尝试删除源对象时应该发生的情况。

拒绝:如果关系目标(员工)上至少有一个对象,请不要删除源对象(部门)。 例如 ,如果要删除部门,则必须确保首先将该部门中的所有员工转移到其他地方; 否则,无法删除该部门。

Nullify:删除对象之间的关系,但不要删除任何一个对象。 仅当雇员的部门关系是可选的,或者您确保在下一次保存操作之前为每个雇员设置一个新部门时,这才有意义。

级联:删除源时,删除关系目标处的对象。 例如 ,如果删除部门,请同时解雇该部门的所有员工。

无操作:对关系目标处的对象执行任何操作例如 ,如果删除部门,则即使所有员工仍认为自己属于该部门,也请保留原样。

问:如何进行核心数据迁移?

持久性存储绑定到数据模型的特定版本。 它保留对数据模型标识符的引用。 如果数据模型发生更改,我们需要告诉Core Data如何将持久性存储的数据迁移到新的数据模型版本。

问: 轻量级VS重度迁移?

轻量级迁移:从最后开始,几乎不需要做任何工作。 由于成本太低,建议选择轻量级迁移而不是重迁移。 由于它可以处理酒糟变化,因此它的功能不如大规模迁移。 例如,轻量级迁移使您可以添加或重命名属性和实体,但不能修改属性的类型或现有实体之间的关系。

为了支持向CoreDataManager类的轻量级迁移,我们需要进行一些小的更改。 我们将带有两个键的选项字典传递给addPersistentStore(ofType:configurationName:at:options:)

  • NSInferMappingModelAutomaticallyOption :如果此键的值设置为true ,则Core Data会尝试根据数据模型的数据模型版本来推断迁移的映射模型。
  • NSMigratePersistentStoresAutomaticallyOption :通过将此键的值设置为true ,我们告诉Core Data如果检测到不兼容,则自动执行迁移。

繁重的迁移是强大的,但这种力量是有代价的。 需要进行大量工作和测试才能确保迁移成功完成,更重要的是要确保迁移不会丢失数据。当我们进行更改时,Core Data无法通过比较数据模型的版本自动推断出该数据时使用它。 然后,核心数据将需要一个映射模型来了解数据模型的版本之间如何相互关联 。 要实现它,我们需要创建自定义映射模型。

问:在Core Data中执行多线程的最佳方法是什么?

根据Marcus Zarra所说,最好的方法不是最快的,但到目前为止,它是最简单,最可维护的。 它依赖于Apple在iOS 6中引入的API,该API可以定义子MOC并指定MOC的并发类型。 Zarra提出的设计基于NSManagedDocument工作方式和使用方式:

  • 单个持久性存储协调器。
  • 私有MOC是唯一实际访问PSC的MOC。
  • 与UI关联的主MOC,它是私有MOC的子代。
  • 特定于辅助线程的多个子MOC。

此设计的优点是,子MOC中的所有更改都将自动传播到其父MOC,从而消除了合并的需要。

Zarra说,这种设计主要缺点是速度慢 ,尽管只有几个百分点。 棘手的问题是,如果执行过多的异步操作,则可能会对UI产生连锁反应,因为与其关联的MOC会依次接收到可能彼此不一致的多个更改。 关于此设计的一个重要细节是,最好不要重复使用价格低廉的子MOC。 另一方面,应将长寿命的子MOC与主MOC手动保持同步,因为更改仅从子MOC传播到其父MOC,而不是相反。

问:什么是理想的Core Data Stack?

理想的核心数据堆栈:

链接到持久性存储协调器的托管对象上下文与主线程无关。 相反,它在后台线程上运行并运行。 当私有管理对象上下文保存其更改时,将在该后台线程上执行写操作。

私有托管对象上下文具有子托管对象上下文,该子托管对象上下文用作应用程序的主要托管对象上下文。 在这种情况下,父级和子级管理对象上下文的概念是关键。

在大多数情况下,管理对象上下文与持久性存储协调器关联。 当此类托管对象上下文保存其更改时,会将其推入持久性存储协调器,协调器进一步将更改推入持久性存储,例如SQLite数据库。

子托管对象上下文没有对持久性存储协调器的引用。 而是保留对另一个托管对象上下文(父托管对象上下文)的引用。 当子托管对象上下文保存其更改时,会将其推入父托管对象上下文。 换句话说,当子托管对象上下文保存其更改时,持久性存储协调器不会意识到保存操作。 仅当父级受管对象上下文执行保存操作时,更改才被推送到持久性存储协调器,然后被推送到持久性存储。

因为当子管理对象上下文保存其更改时不执行任何写操作,所以执行该操作的线程不会被写操作阻塞。 这就是为什么应用程序的主要托管对象上下文是在后台线程上运行的托管对象上下文的子托管对象上下文。