故障是核心数据的重要组成部分。 即使该术语听起来不祥,但故障是Core Data记录的生命周期固有的。 在本教程中,您将学习什么是故障,如何处理故障以及如何调试与故障相关的问题。
先决条件
核心数据是一个高级主题,因此我假设您已经熟悉Xcode和iOS开发。 即使本教程将使用Swift编程语言,本教程中的所有内容也适用于Objective-C。
瓦特是错吗?
得益于Apple核心数据团队的辛勤工作,Core Data擅长于其工作。 核心数据经过高度优化,可以在不牺牲性能的情况下保持较低的内存占用。 错误是Core Data消耗尽可能少的内存的技术之一。
故障并非核心数据所独有。 许多其他框架(例如Ember和Ruby on Rails)也使用了类似的技术。 这个想法很简单,仅在需要时才加载数据。 为了使故障起作用,Core Data通过在编译时创建代表故障的自定义子类来做一些魔术。 让我们来看一个例子。
我创建了一个简单的示例应用程序来使用。 从GitHub下载Xcode项目,然后在Xcode中打开它。 该项目使用Swift 2.1,这意味着您需要Xcode 7.1或更高版本才能满足编译器的要求。
数据模型包含两个实体, List和Item 。 列表可以包含零个或多个项目,并且一个项目始终链接到列表。 这是一对多关系的经典示例。
![资料模型](https://i-blog.csdnimg.cn/blog_migrate/2e608c2ff3bdae7c388198ac23e5cae8.png)
运行应用程序,并通过创建一些列表和项目,用一些数据填充持久存储SQLite数据库。 完成后退出应用程序。
触发故障
您现在应该拥有一个包含一些数据的应用程序。 让我们通过添加一些打印语句来查看故障在实际中是如何工作的。 打开ListsViewController.swift并查找prepareForSegue(_:sender:)
。 在这种方法中,我们获取用户在表视图中选择的列表。 取消注释prepareForSegue(_:sender:)
的打印语句,如下面的实现所示。
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == SegueListViewController {
guard let indexPath = tableView.indexPathForSelectedRow else { return }
// Fetch List
let list = self.fetchedResultsController.objectAtIndexPath(indexPath) as! List
print("1: \(list)")
if let items = list.items {
print("2: \(items)")
print("3: \(items.count)")
print("4: \(items)")
if let item = items.anyObject() {
print("5: \(item)")
print("6: \(item.name)")
print("7: \(item)")
}
}
print("8: \(list)")
// Fetch Destination View Controller
let listViewController = segue.destinationViewController as! ListViewController
// Configure View Controller
listViewController.list = list
}
}
运行该应用程序,然后在列表视图控制器中点击列表之一。 仅当您单击一个列表具有一个或多个项目时,此示例才有意义。 让我们逐步检查打印语句的输出。
1: <Faulting.List: 0x154e5d0b0> (entity: List; id: 0xd0000000001c0000 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/List/p7> ; data: {
items = "<relationship fault: 0x154e3d5b0 'items'>";
name = "List 6";
})
首先要注意的是类名称Faulting.List
。 这是我们期望的,因为模块名为Faulting ,而类名为List 。 在Swift中,类的完整类名称由模块的名称和类的名称组成。
控制台中的输出还显示实体的名称, List和数据字典。 name属性设置为列表6,而items属性标记为关系故障 。 这是核心数据中的第一类故障。 Core Data知道不需要加载关系。 相反,它将关系标记为错误。 如您在下面看到的,第二份印刷声明确认了这一点。
2: Relationship 'items' fault on managed object (0x154e5d0b0) <Faulting.List: 0x154e5d0b0> (entity: List; id: 0xd0000000001c0000 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/List/p7> ; data: {
items = "<relationship fault: 0x154e3d5b0 'items'>";
name = "List 6";
})
但是,第三个打印语句更有趣。 它打印与列表关联的项目数。
3: 2
核心数据只能通过触发关系故障来做到这一点。 它向持久性存储请求与列表关联的项目。 但是,这只是故事的一部分,如第四份印刷品声明所示。
4: Relationship 'items' on managed object (0x154e5d0b0) <Faulting.List: 0x154e5d0b0> (entity: List; id: 0xd0000000001c0000 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/List/p7> ; data: {
items = (
"0xd000000000540002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p21>",
"0xd000000000580002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p22>"
);
name = "List 6";
}) with objects {(
<Faulting.Item: 0x154e83d60> (entity: Item; id: 0xd000000000540002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p21> ; data: <fault>),
<Faulting.Item: 0x154e55e50> (entity: Item; id: 0xd000000000580002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p22> ; data: <fault>)
)}
我们不再看到关系错误,但仍然看到错误。 那是什么意思 核心数据只能通过触发或解决关系错误来为我们提供列表项的数量。 但是,这并不意味着Core Data可以解决关系项。 控制台中的输出确认了这一点。
我们可以看到项目的记录在那里,包括Core Data在内部使用的标识符。 但是,数据字典被标记为故障。 同样,核心数据仅能满足我们的要求。 幸运的是,实质性细节由Core Data处理。
让我们深入一点,从列表中获取其中一项。 我们通过在items
对象上调用anyObject()
来实现。 我们在第五个打印语句中打印结果项目。
5: <Faulting.Item: 0x144d7f100> (entity: Item; id: 0xd000000000540002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p21> ; data: <fault>)
输出不应让您感到惊讶。 输出确认我们正在处理Item实体。 毫不奇怪,数据字典仍然被标记为故障。 在第六个打印语句中,我们打印项目的名称属性。
6: Item 0
因为我们要求记录的属性值,所以Core Data会触发错误以提供该值。 它获取该项目的数据并填充数据字典。 第七份印刷声明证实了这些发现。
7: <Faulting.Item: 0x144d7f100> (entity: Item; id: 0xd000000000540002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p21> ; data: {
list = "0xd0000000001c0000 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/List/p7>";
name = "Item 0";
})
数据字典包含名称属性以及列表关系。 第八条也是最后一条打印语句表明list
对象的关系故障已解决。
8: <Faulting.List: 0x144e55da0> (entity: List; id: 0xd0000000001c0000 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/List/p7> ; data: {
items = (
"0xd000000000540002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p21>",
"0xd000000000580002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p22>"
);
name = "List 6";
})
无法完成故障
我决定写这篇文章来解释一个问题,许多使用Core Data的开发人员会在某个时间点遇到另一个问题,从而引发无法实现的错误。 问题很简单。 假设您有一个包含许多项目的列表,并且用户有时会删除该列表。 该列表中的项目将如何处理? 如果Core Data尝试触发属于该列表的项目之一的列表关系,会发生什么? 让我们找出答案。
让我们重新访问从GitHub下载的项目。 打开Faulting.xcdatamodeld ,项目的数据模型。 选择“ 列表”实体的项目关系,然后打开右侧的“ 数据模型检查器 ”。 我们感兴趣的是删除规则 ,该规则当前设置为Cascade 。 这意味着删除列表时,列表中的每个项目都会被删除。 这很有意义,因为我们不想放弃与列表无关的项目。
![删除规则级联](https://i-blog.csdnimg.cn/blog_migrate/f9e7780f9219cfd27fa8505b32a5f67b.png)
选择Item实体的列表关系,然后打开Data Model Inspector 。 此关系的Delete Rule设置为Nullify 。 这意味着当删除目标记录(项目)时,关系的目标(列表对象)设置为null。 这是关系的默认删除规则。
![删除规则无效](https://cms-assets.tutsplus.com/uploads/users/41/posts/25157/image/figure-data-model-inspector-2.jpg)
运行应用程序,创建一些列表和项目,然后点击左上方的“ 项目”按钮以显示您创建的每个项目。 点按左上角的“ 取消 ”,删除列表之一,然后再次点按“ 项目”按钮以查看更改。 与预期的一样,与已删除列表关联的项目也已被Core Data删除。 这不是魔术。 核心数据仅执行我们在数据模型中定义的删除规则。
我们可以得出结论,为此类应用程序正确建立了关系。 但是,如果我们没有正确配置删除规则会发生什么。 打开数据模型,并将关系, 项目和列表的删除规则设置为No Action 。 再次启动该应用程序,并创建一些列表和项目。 如果删除列表,然后单击左上角的“ 项目”按钮以查看永久性存储中的每个项目,则与已删除列表关联的项目仍应存在。 即使删除了它们所属的列表,也没有删除它们。
点击列表之一,看看会发生什么。 如果您在iOS 8上运行该应用程序,则该应用程序将由于未捕获的异常而崩溃。 如果您在iOS 9上运行该应用程序,那么您只会在控制台中看到错误。 别再挠头了,让我们一起解决这个问题。
无法访问对象
即使应用程序在iOS 8上崩溃,我们在控制台中看到的输出仍然清晰明了。
Faulting[7189:2427906] *** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x175b2ba0 <x-coredata://3920E0C9-6613-47E6-AA19-66AF6555140A/List/p1>''
首先要注意的是,由于未捕获的异常,应用程序崩溃了。 但是,对于我们的讨论而言,更重要的是引发异常的原因。 核心数据告诉我们,无法解决一段关系上的错误。 核心数据所指的关系是我们在表格视图中点击的项目的列表关系。
如果您查看tableView(tableView:didSelectRowAtIndexPath:)
,那么您将了解为何Core Data引发异常。 在这种方法中,我们获取用户点击的项目,并将列表的名称打印到控制台。
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if let item = self.fetchedResultsController.objectAtIndexPath(indexPath) as? Item {
print(item.list?.name)
}
}
由于列表关系是故障,因此Core Data需要触发该故障来解决。 这意味着Core Data向持久性存储请求列表记录。 问题在于记录不再位于持久性存储中,这就是引发异常的原因。
删除无法访问的故障
直到iOS 9为止,这始终是默认行为。 从iOS 9开始,Core Data不再引发异常。 而是将一条隐秘消息记录到控制台。 尽管消息不清楚,但仍然包含问题的原因。
Faulting[2806:1306995] CoreData: warning: An NSManagedObjectContext delegate overrode fault handling behavior to silently delete the object with ID '0xd000000000140000 <x-coredata://396CF04E-B8B4-4108-8719-3C651F880C33/List/p5>' and substitute nil/0 for all property values instead of throwing.
要点是,当无法执行故障时,Core Data不再引发异常。 好消息是,如果您没有捕获到异常,您的应用程序将不再崩溃。
在iOS 9上不引发异常的原因是由于在NSManagedObjectContext
类上引入了新属性shouldDeleteInaccessibleFaults
和新方法shouldHandleInaccessibleFault(_:forObjectID:triggeredByProperty:)
。 在本教程中,我不会介绍这些新增内容。
您需要记住的是,默认情况下,iOS 9 shouldDeleteInaccessibleFaults
设置为true
。 这意味着无法访问的管理对象被标记为已删除,并且其属性被清零。 如果shouldDeleteInaccessibleFaults
设置为false
,则Core Data将通过引发异常来恢复为旧行为。
当然, shouldDeleteInaccessibleFaults
属性应与shouldHandleInaccessibleFault(_:forObjectID:triggeredByProperty:)
。 如果重写此方法,则可以更优雅地处理无法访问的对象。
处理故障
当由于Core Data无法执行故障而遇到异常时,您可能会认为Core Data有点攻击性。 对于刚接触该框架的人员来说,这种反应非常普遍。 事实是,开发商有过错。 设计核心数据时会考虑一组特定的目标,而错误是实现这些目标的重要组成部分。
尽管有它们的名字,但错误应该总是可以实现的。 如果无法解决故障,则意味着数据模型设置不正确,或者应用程序未遵循Core Data框架规定的规则。
在《 核心数据编程指南》中 ,Apple列出了许多可能导致无法解决的故障。 最常见的情况是配置错误的关系(删除规则)和删除托管对象,而应用程序仍然对该托管对象有很强的引用。
请注意,还有其他可能的情况。 根据我的经验,由于核心数据堆栈的问题,开发人员经常会遇到类似的问题。 例如,如果应用程序对托管对象保持强烈引用,并在某个时候取消分配该托管对象的托管对象上下文,则Core Data将不再能够对该托管对象执行任何故障。 我希望您了解如果您遵守Core Data的规则,就不会发生这种情况。
结论
核心数据故障非常有用,并且是Apple持久性框架的关键组成部分。 我希望本文能教您如何处理错误,以及遇到无法实现的错误时应在哪里寻找。 如果您有任何疑问,请随时将其留在下面的评论中,或通过Twitter与我联系。
翻译自: https://code.tutsplus.com/tutorials/what-is-a-core-data-fault--cms-25157