核心数据子上下文不预取关系

Posted

技术标签:

【中文标题】核心数据子上下文不预取关系【英文标题】:Core Data child contexts not prefetching relationships 【发布时间】:2015-12-01 10:24:54 【问题描述】:

我在我的应用方案中激活了 Core Data 调试器 -com.apple.CoreData.SQLDebug 1,并为一个名为 Category 的实体和一个名为 Image 的关系实体获得了以下结果:

主上下文中的 FetchRequest:

NSFetchRequest *fr = [[NSFetchRequest alloc] initWithEntityName:@"Category"];
fr.relationshipKeyPathsForPrefetching = @[@"image"];

NSArray *results = [self.mainContext executeFetchRequest:fr error:nil];

for (Category *category in results) 

    NSLog(@"%@", category.image.width);

控制台日志显示没有出现任何错误 - 这是自为预取设置图像关系以来的预期行为。

对子上下文的相同请求:

NSManagedObjectContext *privateMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[privateMOC setParentContext:self.mainContext];

[privateMOC performBlock:^

        NSFetchRequest *fr = [[NSFetchRequest alloc] initWithEntityName:@"Category"];
        fr.relationshipKeyPathsForPrefetching = @[@"image"];

        NSArray *results = [privateMOC executeFetchRequest:fr error:nil];

        for (Category *category in results) 

            NSLog(@"%@",category.image.width);
        
    ];

在这种情况下,控制台会显示每个图像(其中 4 个)的 Core Data 执行错误。这是一个错误、预期的行为还是我遗漏了什么?

【问题讨论】:

这就是为什么我切换到realm.io而不是核心数据! 我的父上下文试图从 sqlte 存储中获取临时关系。这可能是相同或相似的错误。 预取的目的是什么?如果你 not 预取,你可以让 Core Data 在你真正需要的时候自动获取图像。如果您担心内存或持久存储性能,您可能首先不应该将大图像存储在持久存储中。 Core Data 的嵌套上下文中存在大量各种错误,如您所描述的,或者忽略fetchLimit 等。您确定需要使用它们吗?它们最初是为了增加数据操作的粒度而设计的,而不是为了并发。如果您的目标是并发 - 使用私有队列甚至限制类型上下文,附加到它自己的NSPersistentStoreCoordinator 顺便说一句,关于领域。在 DB 中有大约 50 万条记录的实际应用程序中,如果不使用反向关系,我在导入 Realm 时的性能与使用 Core Data 时的性能大致相同。那个,以及此时更改通知的非常糟糕的实现,以及 Realm 中资源贪婪和低效的 FRC 类似物......我想我会在接下来的几年里坚持使用 Core Data。 【参考方案1】:

实验:

我在一个测试应用程序中复制了您的场景并得出了相同的结论:预取没有发生在后台线程中。日志:

******************************** FOREGROUND **************************************

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTIMESTAMP, t0.ZTITLE, t0.ZIMAGE FROM ZCATEGORY t0 
CoreData: annotation: sql connection fetch time: 0.0004s
CoreData: annotation: Bound intarray values.
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZURL FROM ZIMAGE t0 WHERE  t0.Z_PK IN (SELECT * FROM _Z_intarray0)  
CoreData: annotation: sql connection fetch time: 0.0006s
CoreData: annotation: total fetch execution time: 0.0010s for 4 rows.
CoreData: annotation: Prefetching with key 'image'.  Got 4 rows.
CoreData: annotation: total fetch execution time: 0.0035s for 4 rows.

******************************** BACKGROUND **************************************

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTIMESTAMP, t0.ZTITLE, t0.ZIMAGE FROM ZCATEGORY t0 
CoreData: annotation: sql connection fetch time: 0.0003s
CoreData: annotation: total fetch execution time: 0.0005s for 4 rows.

分析:

根据NSFetchRequest 的documentation,解释说提供预取是为了解决特定的性能挑战。描述如下(我的重点):

预取允许 Core Data 在一次提取(每个实体)中获取相关对象,而不是在触发错误时为每个单独的记录引发对存储的后续访问。例如,给定一个与 Department 实体有关系的 Employee 实体,如果您获取所有员工,然后为每个员工打印出他们的姓名和他们所属部门的名称,这可能是一个错误必须被解雇每个单独的部门对象(有关更多详细信息,请参阅核心数据编程指南中的核心数据性能)。这可能会带来很大的开销。您可以通过在 Employee fetch 中预取部门关系来避免这种情况...

从措辞可以看出,这不是一个必要的设备来提高性能。这可以解释为什么它没有在后台线程上实现:讨论中描述的性能问题似乎表明涉及 UI 更新的典型场景。在我看来,这阐明了此功能的意图。

如果您已经在后台线程上,那么这种性能调整肯定不那么重要,并且通常的故障机制可以充分处理必要的优化。属性relationshipKeyPathsForPrefetching 将因此恢复为其默认值,即一个空数组。

【讨论】:

投反对票时,请说明你的情况。 我也有同样的问题。与单个预取相比,在后台线程上触发数千个错误并不会更快,它很慢并且需要很长时间。所以我不认为这可以解释为任何有意的优化。 这与后台线程(即私有队列)无关,这似乎是使用子上下文的问题。例如,如果您使用私有队列上下文和单独的 PSC,就像苹果在“地震”示例中所做的那样,那么这不会发生。 我认为在访问图像时扩展显示查询的日志会很有帮助。因为记住只是因为它是一个错误并不意味着它不在行缓存中。我不确定子上下文是否共享行缓存。

以上是关于核心数据子上下文不预取关系的主要内容,如果未能解决你的问题,请参考以下文章

CoreData 预取在属性中没有错误

获取后核心数据关系为零

核心数据父/子上下文保存失败

核心数据:更新子上下文

带有子上下文的核心数据多线程

在子上下文中保存核心数据不起作用