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

时间:2014-06-24 22:34:52

标签: objective-c core-data uiimageview uiimage

我的应用程序具有一些视图,可以呈现有关实体和UIScrollView的一些详细信息,其中包含一些相关缩略图的UIImageView(全部保存在核心数据中)。当用户按下缩略图时,我获取完整大小的图像(来自核心数据)并在UIImageView中渲染它。图像从设备相机或相册中捕获到Core Data。保存/检索UIImage工作得很好。按照Apple的建议对数据进行标准化,以便在需要之前最大限度地加载大型对象。

对于这篇长篇文章感到抱歉,但想要彻底......

核心数据设置:

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

问题:

加载图像(缩略图和完整尺寸)时,ImageIO_PNG_Data永远不会被释放。永远。即使在记忆警告之后。当然,最终的崩溃完美无缺:)

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

未发布的数据在工具中显示为 ImageIO_PNG_Data 。通常它们是14.77 MB,因为它们被相机捕获(5s)并且我认为它们是完全解压缩的大小。 ImageIO_PNG_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的image属性设置为nil
    • 使用UIImage *image = [[UIImage alloc] initWithData:UIImagePNGRepresentation(itemImage.image.image)];创建包含Core Data对象副本的图像。这导致分配的内存增加一倍,直到UIImageViews被释放,并且它释放了一半的内存,就像你期望的那样。这似乎证明了UIImageViews不是那种额外的内存。 (这在代码中注释)
    • 尝试使用[managedObjectContext refreshObject:itemImage.image mergeChanges:false]说服核心数据,我真的不关心这些数据,它可以让它发挥作用
    • 尝试使用在image属性上使用的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

2 个答案:

答案 0 :(得分:3)

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

要重置您需要致电的NSManagedObject

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

这将把对象变成一个错误,它的所有值都将从内存中删除。

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

答案 1 :(得分:0)

大多数时候,记忆问题日益严重,原因有两种:

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

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

检查您是否使用更新的Lazy加载库。 使用内存分配工具并检查虚拟机分配,如果您有任何有关问题的IMAGEIO,那么您可能会在NSMainBundle中包含高分辨率图像。

以所需分辨率调整高分辨率图像的大小,并使用imageWithContentsOfFile而不是imageName。