NSManagedObject 的 KVO 方法调用外部二进制数据属性导致表格视图滚动迟缓,为啥?

Posted

技术标签:

【中文标题】NSManagedObject 的 KVO 方法调用外部二进制数据属性导致表格视图滚动迟缓,为啥?【英文标题】:NSManagedObject's KVO method calls for external binary data attributes make table view scrolling sluggish, why?NSManagedObject 的 KVO 方法调用外部二进制数据属性导致表格视图滚动迟缓,为什么? 【发布时间】:2012-08-08 22:23:10 【问题描述】:

经过数周的调试,我终于找到了表格视图滚动期间零星滞后的罪魁祸首。它们是对外部二进制数据属性的-willChangeValueForKey:-didChangeValueForKey: 调用。

为方便起见,您可以将我的应用程序视为 Twitter 客户端。所以主要实体是Tweet,它有一个thumbnail_pic_data 属性,我将其设置为allowsExternalBinaryDataStorage。一个名为thumbnail_picture 的相应非持久属性被用作其方便的访问器。

表格视图是一个推文列表视图——时间线,每条推文的thumbnail_picture 都显示在其相应单元格的内联中。图片是懒惰下载的。下载成功后,我用自定义设置器设置thumbnail_pic_data如下:

- (void)setThumbnail_pic_data:(NSData *)thumbnail_pic_data 
    [self willChangeValueForKey:@"thumbnail_pic_data"]; // culprit
    [self setPrimitiveThumbnail_pic_data:thumbnail_pic_data];
    [self didChangeValueForKey:@"thumbnail_pic_data"]; // culprit

    UIImage *picture;
    if (thumbnail_pic_data) 
        picture = [UIImage imageWithData:thumbnail_pic_data];
    
    self.thumbnail_picture = picture;

使用上面的代码,我在每张图片下载后的表格视图滚动过程中看到零星的滞后。在我注释掉 willChangeValueForKey:didChangeValueForKey: 电话后,滞后就消失了。所以我知道他们是罪魁祸首。

但是,我用下面的代码得到的计时结果显示,它们并没有直接长时间使用CPU:

CFAbsoluteTime t1 = CFAbsoluteTimeGetCurrent();
[self willChangeValueForKey:@"thumbnail_pic_data"];
CFAbsoluteTime t2 = CFAbsoluteTimeGetCurrent();
NSLog(@"willChangeValueForKey time: %f", t2 - t1);
[self setPrimitiveThumbnail_pic_data:thumbnail_pic_data];
CFAbsoluteTime t3 = CFAbsoluteTimeGetCurrent();
NSLog(@"setPrimitiveThumbnail_pic_data time: %f", t3 - t2);
[self didChangeValueForKey:@"thumbnail_pic_data"];
CFAbsoluteTime t4 = CFAbsoluteTimeGetCurrent();
NSLog(@"didChangeValueForKey time: %f", t4 - t3);

计时结果:

willChangeValueForKey 时间:0.000145 setPrimitiveThumbnail_pic_data 时间:0.001512 didChangeValueForKey 时间:0.001810

willChangeValueForKey 时间:0.000138 setPrimitiveThumbnail_pic_data 时间:0.001418 didChangeValueForKey 时间:0.002211

willChangeValueForKey 时间:0.000302 setPrimitiveThumbnail_pic_data 时间:0.001891 didChangeValueForKey 时间:0.003349

willChangeValueForKey 时间:0.000162 setPrimitiveThumbnail_pic_data 时间:0.001462 didChangeValueForKey 时间:0.002114

作为一种临时解决方法,我会将这两个 KVO 调用注释掉。我不确定Core Data Programming Guide 是否会发生任何不好的事情说:

您必须确保调用了相关的访问和更改通知方法(willAccessValueForKey:、didAccessValueForKey:、willChangeValueForKey:、didChangeValueForKey:、willChangeValueForKey:withSetMutation:usingObjects: 和 didChangeValueForKey:withSetMutation:usingObjects:)。

最重要的是,我想知道为什么这两个 KVO 调用会导致表格视图滚动缓慢,希望我能找到更好的解决方法。

【问题讨论】:

缓慢行为意味着 CPU 密集型进程正在主线程中运行。如果willChangeValueForKeydidChangeValueForKey 在当前(主)线程中创建进程,这就是你得到这些结果的原因怎么办?您是否尝试过在后台线程中运行 setThumbnail_pic_data:? @rvil 但 Core Data 不是线程安全的。 【参考方案1】:

请允许我提出一种不同的方法,而不是将数据/图像保存为瞬态属性,而是将图像的名称保存为瞬态属性。图像的 NSData 对象对您来说似乎没有用,因此,不要浪费内存来保存它,因为您似乎唯一需要内容的就是生成图像,而是使用来自网络的 NSData 创建 NSImage 对象, 并将其分配给一个 NSCache 值,其中图像的名称(您的 NSManagedObject 对象的瞬态属性)作为图像对象的键。

【讨论】:

NSCache 不会在会话中持续存在,是吗? 没有。当内存受限时,您可以刷新。 但我需要持久存储。 好的。在实体上使用现有图像的数据属性,创建 UIImage 对象,将其存储在 NSCache 对象上。当您尝试从 NSCache 对象中获取数据时,请验证它是否确实存在。如果没有,则创建一个新的 UIImage 对象并将其存储在缓存中。如果内存受到限制,缓存将被刷新。另一件事:您是否将图像的数据(延迟加载时)保存在主线程中? 首先,我已经在为 UIImage 使用 NSCache。其次,Core Data 不是线程安全的,所以我必须在主线程上调用 setThumbnail_pic_data。【参考方案2】:

终于找到了。

-willChangeValueForKey:-didChangeValueForKey: 将导致调用 NSFetchedResultsControllerDelegate 方法,如果在我的情况下在滚动过程中频繁调用它们,那么这些方法将非常耗时。省略这两个 KVO 调用可以让我避免那些不必要的 NSFetchedResultsControllerDelegate 方法调用,但我不应该这样做,因为 Apple doc 明确告诉我们不要这样做,而且我确实看到如果这样做会发生不好的事情。

本质上,这个问题要求与How to know which property is changed for NSFetchedResultsChangeUpdate?相同的解决方案

【讨论】:

恕我直言,这是一种强制解决方法,而不是解决方案,并且违反了核心数据文档。 @J2theC 是的,我不是说这是解决方案,我只是说这是原因。仍在寻找解决方案。 我也阅读了您的其他问题。那么当你的控制器响应 NSFetchedResultsControllerDelegate 方法时,当缩略图数据发生变化时它的响应比它对其他变化的响应成本高得多? @CarlVeazey 缩略图更改比其他更改频繁得多。 所以你说从你的获取结果控制器的调用是不必要的,那么你用什么来刷新你的缩略图的显示?

以上是关于NSManagedObject 的 KVO 方法调用外部二进制数据属性导致表格视图滚动迟缓,为啥?的主要内容,如果未能解决你的问题,请参考以下文章

-[NSManagedObject willTurnIntoFault] 是不是禁用 KVO 通知?

为啥我使用 NSManagedObject 在 self 上获得基于块的崩溃清除 KVO?

Swift 中带有 Core Data 的 KVO 通知

KVO:如何告诉观察者,那个自我改变了?

如何告诉(托管)对象通知其 KVO 需要重新缓存其属性之一?

保存时检测对 NSManagedObject 的更改