这是苹果官方文档 Core Data Programming Guide 的渣翻译。
引用决定了什么时候一个托管对象从内存中被释放,也影响了它们的引用计数的增加。
托管对象和上下文之间的弱引用
托管对象知道它们关联的上下文是哪一个,而托管对象上下文也知道它们拥有的托管对象。虽然默认情况下,它们之间的引用是弱引用。这就意味着一般情况下你不能让一个上下文保证一个托管对象实例的可用性,想反过来你也不能依靠一个托管对象去保证一个上下文的可用性。换个说法就是,你获取了一个对象,但是并不意味着这个对象能够一直常驻。
例外的就是,托管对象上下文维护这所有改动过的(插入、删除或者更改)的对象的强引用,一直到这个转换被提交(利用一个save:方法)、或者舍弃变更(使用重设或者回滚)。注意撤销管理器也会持有托管对象的强引用。
在托管对象和上下文之间创建一个强引用
你可以修改一个上下文的默认行为,使用设置retainsRegisteredObjects成true,让它的托管对象引用保持为强引用。这样做会让托管对象的生命周期是依赖于上下文的生命周期的。当你仅需要缓存小量数据在内存中的时候这种依赖是十分方便的。例如,设想这个上下文掌控者一个在一个单事件循环之前持久化的一些临时数据,譬如OS X应用的的一个表格或者一个iOS应用中的模态视图。当时使用多线程并且在线程之间传输数据的时候,让托管对象依赖于上下文也是十分有用的,例如,你在后台线程查询数据,然后把对象ID传给主线程。在主线程能够完全使用到这些ID提取本地实例之前,后台线程需要一直为主线程预提取的这些对象保持强引用。因此这些对象要能够从内存中脱离出来并且从硬盘中再次查询出来。
用一个独立的容器去保持那些你需要的托管对象的强引用。你可以用一个数组或者字典,或者一个对管理的对象拥有强引用的对象控制器(例如,一个NSArrayController实例)。在某个时间那些你不需要的托管对象会被销毁回收,例如当关系被清除的时候(参考去除对象之间的强引用))。
如果你结束了一个托管对象上下文,或者由于其他原因你想要断开某个上下文和持久化存储协调器的链接,不要设置上下文的协调器属性为nil。
取而代之,只需要简单地放弃对这个上下文的拥有权,让它自行被销毁回收。
OBJECTIVE-C
[self setMyManagedObjectContext:nil];
SWIFT
[self setMyManagedObjectContext:nil];
<span id="break_strong_reference_between_objects"></span>
去除对象之间的强引用
和托管对象和它们的托管对象上下文之间的默认行为相反,在托管对象之间,每一个对象都维护着一个对其他相关的对象或者对象集合的强引用。这种关系可能使在这些对象的使用期过了之后由于强引用循环仍然长期保留在内存之中。为了确保这个引用循环能被打破,当你结束了使用某个对象,你可以使用托管对象上下文方法refreshObject:mergeChanges:来将托管对象转换成为fault。
一般你还可以使用refreshObject:mergeChanges:来刷洗一个托管对象的属性值。如果这个mergeChanges标识是YES,这个方法就会使用持久化存储协调器中的有效值合并属性值。然而如果标识是NO,这个方法就会粗暴地将这个对象回退称为fault而不执行合并,这样就可以打破相关对象之间的强引用。
在一个托管对象被销毁回收之前,所有对它的强引用必须被移除,包括那些Core Data之外的。也可以参考变更管理
在变更和撤销管理中的强引用
一个上下文会在完成变更操作之前一直保持对托管对象的强引用,知道上下文调用了save:, rollback, 或者dealloc,或者上下文使用了相应的撤销指令来撤销变更。如果一个撤销操作撤销了一个指定对象的所有变更,这个对象的所有强引用就能回复到弱引用。
撤销管理器会关联任何一个持有强引用托管对象的上下文。默认情况下,上下文的撤销管理器持有无限次撤销和重做的栈。为了限制你的应用的内存占用,要确保在适当的时候刷新上下文的撤销栈(使用removeAllActions)。
如果你没有意愿使用Core Data的撤销功能,你可以设置上下文的撤销管理器为nil以降低应用的资源需求。这可能比较适合后台工作线程,或者大量的引入、批处理操作。
确保数据是最新的
如果两个应用同时使用了同一个数据存储,或者一个应用有多个持久化栈,那么就可能在一个托管对象上下文或者持久化存储中的托管对象没有跟仓库内容同步。如果发生了这样的事情,你需要刷新托管对象中的数据和持久化存储(快照)来确保数据值是当下的。
刷新一个对象
属性值是从持久化存储(实例化对象)中封装而来的托管对象,跟将要变更、插入、删除的对象一样,永远不会脱离开发者的干涉而被一个提取操作修改。例如,设想有这样一个场景,你提取了一些对象并且在一个编辑中的上下文中修改了他们;同时,在另一个编辑中上下文中,你修改了同样的数据并且提交了变更。如果在第一个编辑中上下文中,你执行了一个新的提取操作并返回了同样的对象,你不会看到最新提交的数据值 —— 你会看到在当下内存缓存状态中存在的对象。
为了刷新一个托管对象的属性值,你需要使用托管对象上下文的一个方法refreshObject:mergeChanges:。如果mergeChanges标识是YES,这个方法会使用持久化存储协调器中的有效对象来合并托管对象的属性值。如果这个标识是NO,这个方法只会简单将对象置为fault,而不会进行合并操作,同时也会去除指向其他托管对象的强引用。因此你可以使用这个方法整理那些你希望留在内存中的部分对象图。
一个对象的无效间隔就是在存储重新获取快照之前,从创建到被调用的间隔时间。过时间隔仅仅会影响解除faults;进一步说,这仅仅跟可增长式存储(例如SQLite)有关。其他的存储从不会重新提取数据,因为这些数据全部都存在了内存中。
合并临时属性的变更
临时属性就是一个对象不会存储在硬盘中的属性。如果你使用refreshObject:mergeChanges:,并设置mergeChanges为YES,那么在awakeFromFetch调用之后所有的临时属性都会被恢复到预设值。这就意味着,如果你有一个临时属性值是依赖于另一个有预设值的属性的,那么这个临时属性就会变得不同步了。
设想在一个应用中,你有一个Person实体,这个提示有属性firstName和lastName,和一个派生的缓存临时属性fullName,(实践中可能不太可能会缓存一个叫fullNmae的属性,但这样的例子会比较好理解)就可以推断这个fullName会在一个自定义awakeFromFetch方法中计算并缓存。
当下名为“Sarit Smit”的在持久化存储中的一个Employee,被两个托管对象上下文管理着:
- 在上下文1,相应实例的firstName被变更为“Fiona”,这样缓存的fullName就会被更改为“Fiona Smith,上下文被保存。
在持久化存储总,这个employee就是“Fiona Smith”。 - 在上下文2,相应实例的lastName被变更为“Jones”,这样缓存的fullName就会变成“Sarit Jones”。
这个对象之后使用mergeChange为YES刷新了。这个刷新操作去存储中提取到了“Fiona Smith”。这个刷新操作会刷新对象以下数据:- fistName没有在刷新之前变更;所以会从存储中提取到新的值,就是现在的“Fiona”。
- lastName在刷新之前变更了;所以,在刷新之后,它被设置为变更后的值 —— “Jones”。
- 临时值fullName,在刷新之前也被修改了。刷新之后,它的值被恢复成为“Sarit Jones”。然而,正确的值应该是“Fiona Jones”。
上面的例子展示了,由于预设值是在awakeFromFetch之后设置的,你不能使用awakeFromFetch来确保临时值在接着的刷新之后能被正确设置(或者如果你这样做了,这个值随后也会被覆写)。在这些情况下,最好的解决方案是使用一个额外的实例变量来标记刷新操作被执行了,需要重新计算临时值。