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

Jay*_*ian 2 core-data objective-c uiimageview uiimage

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

很抱歉这篇很长的帖子,但想要彻底...

核心数据设置:

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

问题:

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

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

未发布的数据在仪器中显示为ImageIO_PNG_Data。通常它们是 14.77 MB,因为它们是由相机(5 秒)捕获的,我认为这是它们完全解压缩的大小。ImageIO_PNG_Data 对象的数量与我加载的唯一图像的数量完全匹配。稍后加载相同的图像不会增加内存占用,因此看起来是 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];
    }
Run Code Online (Sandbox Code Playgroud)

发布代码:

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

-(void) cleanup{
LogInfo(@"%d images", testImages.count)
for (UIImageView* imageView in testImageViews){
    [imageView removeFromSuperview];
    imageView.image = nil;
}
[testImageViews removeAllObjects];;
}
Run Code Online (Sandbox Code Playgroud)

我试过的

  • 阅读我可以在 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
Run Code Online (Sandbox Code Playgroud)

Mar*_*rra 5

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

要重置NSManagedObject您需要调用:

NSManagedObject *myImageObject = ...;
NSManagedObjectContext *moc = ...;
[moc refreshObject:myImageObject mergeChanges:NO];
Run Code Online (Sandbox Code Playgroud)

这会将对象变成错误,并且它的所有值都将从内存中删除。

您还可以强制上下文宽调用刷新-resetNSManagedObjectContext