Core Data 不会从内存中释放 UIImage 数据 (ImageIO_PNG_Data)

Posted

技术标签:

【中文标题】Core Data 不会从内存中释放 UIImage 数据 (ImageIO_PNG_Data)【英文标题】:Core Data won't release UIImage data from memory (ImageIO_PNG_Data) 【发布时间】:2014-06-24 22:34:52 【问题描述】:

我的应用程序具有呈现有关实体和 UIScrollView 的一些详细信息的视图,其中包含相关缩略图图像的一些 UIImageViews(全部保存在核心数据中)。当用户按下缩略图时,我会(从核心数据)获取完整尺寸的图像并将其呈现在 UIImageView 中。这些图像是从设备相机或相册捕获到 Core Data 的。 UIImage 的保存/检索工作得很好。数据按照 Apple 的建议进行标准化,以尽量减少加载大型对象,直到需要。

抱歉,这篇文章太长了,但我想彻底……

核心数据设置:

***实体是WalkthruItem,它与保存缩略图(可转换)和cmets 的WalkthruItemImage 具有一对多关系。然后将实际的全尺寸图像存储在一对一相关对象WalkthruItemImageImage 中,该对象具有称为“图像”的单个可转换属性。这样做是为了避免在显示所有缩略图时加载完整尺寸的图像。 (无法发布编辑器的图像,抱歉)只有在按下缩略图时才会加载完整尺寸的图像,如下面的代码所示。

问题:

在加载图像(缩略图和全尺寸)时,ImageIO_PNG_Data 永远不会被释放。曾经。即使在内存警告之后。当然,最终的崩溃效果很好:)

我最终将这些图像加载到 UIImageView 中,然后(我认为)正确地释放它们。

未发布的数据在仪器中显示为 ImageIO_PNG_Data。通常它们是 14.77 MB,因为它们是由相机(5 秒)捕获的,我认为这是它们完全解压缩的大小。 ImageIO_PNG_Data 对象的数量与我加载的 unique 图像的数量完全匹配。稍后加载相同的图像不会增加内存占用,因此看起来它是 Core Data 试图为我做一些我真的不需要的缓存。

图片加载代码:

这里是拉取全尺寸图像以响应按下的缩略图然后将其放入 UIImageView 的代码。这个案例有点做作,因为我为这个问题简化了它。通常有一个 segue 和一些花哨的 UIImageView 容器,但这种情况以更容易记录的方式显示了确切的内存问题。一些失败的修复尝试在代码中被注释掉,并在下面的“我尝试了什么”部分中讨论:

if(viewHit.tag>-1)
        currentImageIndexForPreview = viewHit.tag;

        // this object holds the thumbnail
        DCWalkthruItemImage * itemImage = self.walkthruItem.images[currentImageIndexForPreview];

        // itemImage.image (1:1 relationship) gets the DCWalkthruItemImageImage object
        // itemImage.image.image gets the actual image data
        UIImage *image = itemImage.image.image;

        // this below attempt doubles the temporarily allocated memory but then frees half of it when the UIImageViews are dealloced
        // essentially the same problem but seems to prove that it's not the UIImageView holding the reference
        //UIImage *image = [[UIImage alloc] initWithData:UIImagePNGRepresentation(itemImage.image.image)];

        // simplified case of just throwing an imageView up on the controller's view
        UIImageView* imageView = [[UIImageView alloc] initWithImage:image];
        imageView.frame = CGRectMake(testImageViews.count * 10, testImageViews.count * 10, 100, 100);
        imageView.contentMode = UIViewContentModeScaleAspectFill;
        [self.view addSubview:imageView];
        [testImageViews addObject:imageView];

        //  attempts to free references
        itemImage.image.image = nil;
        [self.managedObjectContext refreshObject:itemImage.image mergeChanges:false];
    

发布代码:

我只是通过按下按钮调用此代码来释放 UIImageViews

-(void) cleanup
LogInfo(@"%d images", testImages.count)
for (UIImageView* imageView in testImageViews)
    [imageView removeFromSuperview];
    imageView.image = nil;

[testImageViews removeAllObjects];;

我的尝试

阅读我可以在 SO 上找到的内容,但与核心数据相关的项目似乎并不适用。 大部分都是关于如何存储 UIImage 与 UIImageView 相关的问题似乎都处理从资源或通过 HTTP 加载的图像。这个问题似乎是核心数据保留了内存,而不是我认为我在上面证明的 UIImageView 在 Core Data 中存储时,已切换到使用 NSValueTransformer 强制将数据传入/传出 UIImage 到 NSData。这没有任何区别,因为 Core Data 似乎很聪明,即使没有这个变压器,它也只是为我做到了。该转换器的代码在下面并且目前正在使用,因为我阅读的所有建议都说使用一个,我猜这将是第一个建议。但就像我说的,只是将属性设置为 Transformable 似乎允许 Core Data 为我做这件事。 尝试了以下各种组合: 在将 UIImageView.image 属性从其父视图中删除之前将其设置为 nil 获取数据后将 ManagedObject 的图像属性设置为 nil 使用UIImage *image = [[UIImage alloc] initWithData:UIImagePNGRepresentation(itemImage.image.image)]; 创建带有Core Data 对象副本的图像。这导致分配的内存翻倍,直到 UIImageViews 被释放,并且它释放了一半的内存,正如您所期望的那样。这似乎证明不是 UIImageViews 保留了额外的内存。 (这在代码中有注释) 尝试[managedObjectContext refreshObject:itemImage.image mergeChanges:false] 让 Core Data 相信我真的不关心这些数据,它可以放手 尝试使用图像属性上使用的 NSValueTransformer,结果与我在 Transformable 属性上没有代码时的结果相同。 Core Data 似乎很乐意为我做这种转变。代码的当前状态是使用我的转换器

这是转换器代码

这是在 Core Data 编辑器的属性表的 Transformer Name 属性中设置的。

@interface DCUIImageToNSDataTransformer : NSValueTransformer
@end

#import "DCUIImageToNSDataTransformer.h"


@implementation DCUIImageToNSDataTransformer 



+ (Class)transformedValueClass 
    return [NSData class];


+ (BOOL)allowsReverseTransformation 
    return YES;


- (id)transformedValue:(id)value 
    return UIImagePNGRepresentation(value);


- (id)reverseTransformedValue:(id)value 
    return [[UIImage alloc] initWithData:value];


@end

【问题讨论】:

您最初是如何加载图像的?看起来您的 itemImages 已经加载到其他地方的内存中。 是的,self.walkthruItem 是在 viewDidLoad 中加载的。它的 .images NSSet 只是通过 .images 属性访问。此时代码中的 NSSet 已用于填充 UIScrollView 中的缩略图。该 NSSet 中保存的 WalkthruItemImage 对象仅保存缩略图和 NSString 注释。对 itemImage.image.image 的调用是从 Core Data 中提取实际全尺寸图像的原因。 【参考方案1】:

您需要重置 Core Data 对象,否则它将保留二进制数据。这是在 Core Data 中存储二进制数据不是一个好主意的众多原因之一。您最好将图像存储在磁盘上,并将文件指针仅保留在 Core Data 本身中。

要重置NSManagedObject,您需要调用:

NSManagedObject *myImageObject = ...;
NSManagedObjectContext *moc = ...;
[moc refreshObject:myImageObject mergeChanges:NO];

这会将对象变成一个故障,并且它的所有值都将从内存中删除。

您还可以通过在 NSManagedObjectContext 上调用 -reset 来强制进行上下文范围刷新。

【讨论】:

关于 UIImage 的核心数据与文件系统。到目前为止,Core data 的使用非常简单,级联删除以及几乎为零的代码已经很方便了。随着图像大小和我存储它们的方式,它们可能最终还是在磁盘上。我确实明白你的意思,但我想我会走这条路,直到它以某种方式让我失败。 我确实将该调用作为图像加载代码的最后一行。我会再玩弄它的那一部分,也许会尝试刷新父对象或其他东西。我曾希望这将是 Core Data 释放记忆的关键。 是的,就是这样,谢谢。而不是传递我想要释放的实际对象,我需要传递它的父对象。不知道为什么...也许我应该买你的书 :) 所以不是:[self.managedObjectContext refreshObject:itemImage.image mergeChanges:false]; 我不得不:[self.managedObjectContext refreshObject:itemImage mergeChanges:false]; 在 Objective-C 中是 NO 而不是 false :)【参考方案2】:

越来越多的内存问题大部分时间来自以下两种方式:

1.检查您在代码中使用的图像分辨率,仅使用所需的图像分辨率。

假设您在 1X 中使用 40x40 图像大小,在 2x 中使用 80x80 等等。然后只使用所需的分辨率而不是更多。

检查您正在使用更新的延迟加载库。 使用内存分配工具并检查 VM 分配,如果您有任何关于问题的 IMAGEIO,那么您肯定在 NSMainBundle 中包含高分辨率图像。

将高分辨率图像调整为所需分辨率并使用 imageWithContentsOfFile 代替 imageName。

【讨论】:

以上是关于Core Data 不会从内存中释放 UIImage 数据 (ImageIO_PNG_Data)的主要内容,如果未能解决你的问题,请参考以下文章

从内存存储中删除 Core Data 对象会将它们变成故障,但不会擦除它们

Core Data 删除外部存储实体不会释放 iCloud 中的空间

Core Graphics 栅格数据未从内存中释放

cxf webservice 客户端调用内存溢出,内存不会自动释放,请问各位大侠是怎么解决的。

RestKit / Core Data:远程删除的实体不会从 Core Data 中删除

使用颜色条删除图像不会释放 matplotlib 中的内存?