我的应用程序具有一些视图,可以呈现有关实体和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];;
}
我尝试了什么
UIImage *image = [[UIImage alloc] initWithData:UIImagePNGRepresentation(itemImage.image.image)];
创建包含Core Data对象副本的图像。这导致分配的内存增加一倍,直到UIImageViews被释放,并且它释放了一半的内存,就像你期望的那样。这似乎证明了UIImageViews不是那种额外的内存。 (这在代码中注释)[managedObjectContext refreshObject:itemImage.image mergeChanges:false]
说服核心数据,我真的不关心这些数据,它可以让它发挥作用这是变压器代码:
这是在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
答案 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。