CoreData - 删除所有实体消耗 RAM 并需要很长时间

Posted

技术标签:

【中文标题】CoreData - 删除所有实体消耗 RAM 并需要很长时间【英文标题】:CoreData - delete all entities consumes RAM and take a long time 【发布时间】:2015-08-13 13:28:10 【问题描述】:

我有一个存储测量数据的 CoreData 配置。数据按Session 分组,因此每个Session 实体与SensorData 具有一对多的关系。删除规则设置为Cascade。我还有一个全部删除按钮。当它被按下时,我运行以下代码。

// All sessions (cascading)
[self.context performBlockAndWait:^

    // Fetch only managedObjectID to reduce memory impact
    NSFetchRequest *sessionsRequest = [NSFetchRequest fetchRequestWithEntityName:@"Session"];
    [sessionsRequest setIncludesPropertyValues:NO];

    NSError *sessionsError;
    NSArray *allSessions = [self.context executeFetchRequest:sessionsRequest error:&sessionsError];

    if (sessionsError) 
        NSLog(@"Failed to fetch all sessions. %@", sessionsError);
    

    // Release fetch request
    sessionsRequest = nil;

    // Loop and delete
    int i = 0;
    for (NSManagedObject *session in allSessions) 
        NSLog(@"Deleting session (%d / %d)", ++i, allSessions.count);
        if (i != allSessions.count) 
            [self.context deleteObject:session];
        
    


    NSLog(@"All session deleted.");
];

NSLog(@"Saving.");
[self.context performBlock:^
    [self.document saveToURL:self.documentPath
        forSaveOperation:UIDocumentSaveForOverwriting
       completionHandler:^(BOOL success)
           NSLog(@"Document saved %@.", success ? @"successfully" : @"unsuccessfully");
       ];
];
NSLog(@"Done saving.");

发生的情况是我得到了如下的日志输出,因此执行得相当快。但随后 UI 冻结,RAM 使用量猛增。 大约有 60 个会话和大约 1M 次测量(每次测量 3 个浮点值),最终 RAM 使用量太大,应用程序在 20 分钟左右后崩溃,所有条目仍然存在。

显然节省在这里发挥作用,但我做错了什么?感谢任何指点。


日志输出:

2015-08-13 15:16:56.825 MyApp[4201:1660697] Deleting session (1 / 61)
2015-08-13 15:16:56.826 MyApp[4201:1660697] Deleting session (2 / 61)
.
.
.
2015-08-13 15:16:56.862 MyApp[4201:1660697] Deleting session (60 / 61)
2015-08-13 15:16:56.863 MyApp[4201:1660697] Deleting session (61 / 61)
2015-08-13 15:16:56.863 MyApp[4201:1660697] All session deleted.
2015-08-13 15:16:56.864 MyApp[4201:1660697] Saving.

2015-08-13 15:16:56.864 MyApp[4201:1660697] Done saving.

更新

我编辑了程序以首先删除测量数据并通过设置获取限制来保持 RAM 关闭(如建议的那样)。但是,如果我删除了 SensorData 中的 200 个,则保存大约需要 3 秒,除了前约 1000 个条目。不过删除速度很快。请参阅下面的跟踪。

我想在不删除文档的情况下解决此问题。感觉就像一个 hack(尽管可能是一个很好的)。

追踪

【问题讨论】:

你是如何设置你的 CoreData 堆栈的?你在使用 UIManagedDocument 吗? self.context 是主线程 NSManagedObjectContext 吗?您的操作系统部署目标版本是什么? 看起来您使用的是UIDocument,在这种情况下,删除当前文档可能更容易。 是的,我正在使用 UIDocument。 self.context 是 UIDocument 的 NSManagedObjectContext (即部署目标是 ios8(在标签中模糊地指定)。我还尝试将 performBlock 更改为 performBlockAndWait,执行速度很快。 【参考方案1】:

需要大量内存的是您正在加载所有会话:

NSArray *allSessions = [self.context executeFetchRequest:sessionsRequest error:&sessionsError];

尝试像这样加载例如 100 x 100:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[self.context setUndoManager:nil];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Session" inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
[fetchRequest setIncludesPropertyValues:NO];
[fetchRequest setFetchLimit:100];
NSError *error;
NSArray *items = [self.context executeFetchRequest:fetchRequest error:&error];
while ([items count] > 0) 
    @autoreleasepool 
        for (NSManagedObject *item in items) 
            [self.context deleteObject:item];
        
        if (![self.context save:&error]) 
            NSLog(@"Error deleting %@ - error:%@",self.entityName, error);
        
    
    items = [self.context executeFetchRequest:fetchRequest error:&error];
 

【讨论】:

这不能直接工作,因为大部分数据不包含在Session 中,而是在SensorData 中。因此,即使我将限制设置为 1,它仍然可以获取 100k SensorData。但是如果我用这种方法先直接删除SensorData,然后删除Session,效果很好。但是,删除 200 个SensorData 后,保存最多可能需要 3 秒。任何线索为什么?【参考方案2】:

在不知道上述问题答案的情况下快速解决。我会先删除所有测量数据,然后删除会话数据。但最重要的是,您应该始终在 fetch 请求上设置批量大小。

// Fetch only managedObjectID to reduce memory impact
NSFetchRequest *sessionsRequest = [NSFetchRequest fetchRequestWithEntityName:@"Session"];
[sessionsRequest setIncludesPropertyValues:NO];
[sessionsRequest setFetchBatchSize:20];

接收器的批量大小。

默认值为 0。批量大小为 0 被视为无限,这将禁用批量错误行为。

如果您设置非零批量大小,则执行提取时返回的对象集合将分成多个批次。执行 fetch 时,会评估整个请求并记录所有匹配对象的身份,但一次从持久存储中获取不超过 batchSize 对象的数据。执行请求返回的数组将是一个代理对象,可以根据需要透明地对批次进行故障处理。 (在数据库术语中,这是一个内存游标。)

您可以使用此功能来限制应用程序中的工作数据集。结合 fetchLimit,您可以创建任意结果集的子范围。

出于线程安全的目的,您应该将执行 fetch 时返回的数组代理视为由执行请求的托管对象上下文所拥有,并将其视为在该上下文中注册的托管对象。

【讨论】:

【参考方案3】:

正如 Tom 所说,删除整个文档可能更容易,然后从新文档开始。

iOS9 引入了NSBatchDeleteRequest,它应该对你有所帮助。您可以在此处观看 WWDC 视频:https://developer.apple.com/videos/wwdc/2015/?id=220,视频开始 15 分钟开始讨论该部分。

这是一个类似问题/答案的链接:Core Data: delete all objects of an entity type, ie clear a table

【讨论】:

以上是关于CoreData - 删除所有实体消耗 RAM 并需要很长时间的主要内容,如果未能解决你的问题,请参考以下文章

删除 CoreData 实体中的所有记录

从 TableView 中的 CoreData 中删除实体时出错

SwiftUI - 如何在 CoreData 中删除实体中的行

Coredata 手动迁移

以编程方式在运行时删除或添加属性到CoreData

CoreData不删除对象一对多实体