随着时间的推移,有问题的 NSManagedObject 积累

Posted

技术标签:

【中文标题】随着时间的推移,有问题的 NSManagedObject 积累【英文标题】:Problematic NSManagedObject accumulation over time 【发布时间】:2014-01-27 03:34:01 【问题描述】:

我有一个应用程序,它不断地从 TCP/IP 端点接收 XML 消息流。收到每条消息后,应用程序将其内容消化成一组核心数据实体。这是通过三个上下文结构实现的:

主(专用队列) 主(主队列 -> 主) 流(私有队列 -> 主队列)

这种安排使流处理远离主线程。应用程序通常每秒或每两秒钟接收 10 到 150 条消息。 Stream 上下文的保存发生在每条消息被解构和持久化之后。在 A6 级别的设备上,CPU 使用率通常不足 15%。

然而,我的问题是记忆。如果我将 NSFetchedResultsController 连接到 Main 上下文,我会在消息到达时得到很好的消息流。但是,如果我进行分析,我会注意到我的 NSManagedObject 计数逐渐增加。最终内存压力会导致应用程序终止。

经过 12 分钟的分析,该应用已使用 6300 条 XML 消息并解析了 121,000 个属性。属性消耗 7.8MB,消息消耗 438KB,应用程序总大小现在为 54MB。显然这是不可持续的。

仪器指出所有对象仍处于活动状态。在互联网上闲逛让我相信我可能有一个保留周期,导致对象不会出错。但是,使用“refreshObject”的建议在文档中并不清楚它是否适用于此。

一旦接收到 XML,就会创建一个 Message 实体。接下来,使用 XML 的根节点作为它的名称和相关的位来创建一个 Type 实体。类似地,对于这些元素的每个元素和子元素以及 XML 的任何内联属性,都会创建一个属性元素。这是有趣的部分,因为它引用了消息(用于所有属性的平面表示)以及与自身的分层 childProperties 关系。在这个过程结束时,上下文被保存,主上下文拾取它,FRC 显示新行。

一个想法是在保存每几百条消息后重置 Stream 上下文。如果我断开 FRC,我可以基本保持水平 - 但是这感觉不对,并且在我重新连接 FRC 时并不能解决问题。

任何想法都将不胜感激。

【问题讨论】:

你读过THIS(包括关于插入对象的cmets)吗? 【参考方案1】:

我建议使用与主上下文相同的持久存储协调器来配置您的 Stream 上下文。并且可能会定期重置流上下文。

在当前配置中,流上下文填充对其父级施加了额外的压力。如果 Stream 上下文中发生重大更新,这种压力就会更加明显。

首先,当 Stream 上下文需要做一些需要锁定的事情时,它会锁定双亲。

其次,当 Stream 上下文中发生保存时,所有更改都会被推送回父上下文,即 Main 上下文。而且你无法控制它。如果 Main 上下文中有一个 fetched results 控制器,那么在保存时它将一一重播所有更改。而且如果更新很大,就会带来很大的开销。肯定在 CPU 中,也可能在内存中。

我认为在后台处理大更新和刷新 UI(尤其是使用 fetched results 控制器)的最佳模式是配置直接使用持久存储协调器进行大更新的上下文。然后,当发生大更新时,只需在 UI 上下文中重新获取。并且不要忘记将 fetch 请求上的 fetch batch size 设置为对您的案例值有意义的值。您可以从屏幕上可见的单元格数量开始。

这种模式效率更高,但会带来复杂性成本。您需要考虑如何在其他上下文中刷新数据。您需要注意这一点,因为 Core Data 不会触及完全实现的对象。 (设置 setShouldRefreshRefetchedObjects 也无济于事,因为 Apple 向我确认了这个错误。)

例如,您在 Main 上下文中获取了一些对象,访问了它的属性以将其显示在屏幕上。这个对象不再是一个错误。然后您的 Stream 上下文(现在直接配置了持久存储协调器)更新了相同的属性。即使您在 Main 上下文中重新获取并且该对象将出现在搜索结果中,也不会更新对象属性。

所以你可以使用这样的东西:

- (void)refreshObjectsOnContextDidSaveNotification:(NSNotification *)notification 
    NSSet *updatedObjects = notification.userInfo[NSUpdatedObjectsKey];
    NSSet *updatedObjectIDs = [updatedObjects valueForKey:@"objectID"];

    [self.mainContext performBlock:^
        for (NSManagedObject *object in [self.mainContext registeredObjects]) 
            if (![object isFault] && [updatedObjectIDs containsObject:[object objectID]]) 
                [self.mainContext refreshObject:object mergeChanges:YES];
            
        
    ];

    [self.masterContext performBlock:^
        for (NSManagedObject *object in [self.masterContext registeredObjects]) 
            if (![object isFault] && [updatedObjectIDs containsObject:[object objectID]]) 
                [self.masterContext refreshObject:object mergeChanges:YES];
            
        
    ];

这将刷新主上下文和主上下文中的更新对象。

当 Stream 上下文中的保存不是很大时,您可以使用标准合并方法简单地将更改合并到其他两个上下文中。当使用获取结果控制器时,您将能够看到关于对象删除和插入的漂亮单元格动画。您可以从 context-did-save 通知中用户信息的 NSInsertedObjectsKeyNSUpdatedObjectsKeyNSDeletedObjectsKey 键获得保存中受影响的对象数量。

每次大保存后,您都可以重置 Stream 上下文。只是不要忘记,重置后您无法在此上下文中访问任何以前获取的对象。

【讨论】:

以上是关于随着时间的推移,有问题的 NSManagedObject 积累的主要内容,如果未能解决你的问题,请参考以下文章

随着时间的推移跟踪活动对象

随着时间的推移重复条目计数不同

JavaFX 滚动表更新性能随着时间的推移而降低

RX Observables - 如果我不能改变价值,那么“随着时间的推移”有啥意义?

基本软件合成器的延迟随着时间的推移而增长

如何使用ggplot2随着时间的推移绘制p值?