Jay Eskandarian
Jay Eskandarian

Reputation: 71

Core Data won't release UIImage data from memory (ImageIO_PNG_Data)

My app has view that renders some details about an Entity and a UIScrollView with some UIImageViews of related thumbnail images (all held in core data). When the user presses a thumbnail I fetch the full size image (from core data) and render it in a UIImageView. The images were captured to Core Data from the device camera or photo album. Saving/retrieving of the UIImage works just fine. The data is normalized as recommended by Apple to minimize loading large objects until needed.

Sorry for the long post but wanted to be thorough...

The Core Data setup:

The top level Entity is a WalkthruItem which has a to-many relationship to WalkthruItemImage that holds the thumbnail (Transformable) and comments. The actual full-size images are then stored in a to-one related object WalkthruItemImageImage which has a single Transformable property called 'image'. This is done to keep from loading the full size image when showing all of the thumbnails. (Can't post images of the Editor, sorry) The full size image only gets loaded when the thumbnail is pressed as you'll see in the code below.

The issue:

When loading images (both thumbnails and full size) the ImageIO_PNG_Data never gets released. Ever. Even after a memory warning. Of course the eventual crash works perfectly :)

I eventually load these images into a UIImageView and then (I think) properly release them.

The unreleased data presents as ImageIO_PNG_Data in instruments. Typically they are 14.77 MB as they were captured by the camera (5s) and I assume that's their fully decompressed size. The number of ImageIO_PNG_Data objects exactly matches the number of unique images I've loaded. Loading the same image later does not increase the memory footprint so it looks like it's Core Data trying to do some caching for me that I really don't need.

Image loading code:

Here is the code that pulls the full size image in response to the thumbnail being pressed and then puts it into a UIImageView. The case is a bit contrived since I simplified it for this question. Normally there is a segue and some fancy UIImageView container but this case shows the exact memory issue in a much easier to document fashion. Some failed attempts at a fix are commented out in the code and discussed in the 'What I tried' section below:

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];
    }

Release code:

I just call this code from a button press to release the UIImageViews

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

What I've tried

Here is the transformer code:

This is set up in the Transformer Name property of the property sheet in the Core Data editor.

@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

Upvotes: 2

Views: 1619

Answers (2)

Vinay Vaish
Vinay Vaish

Reputation: 56

Growing memory issue most of the time come cause of 2 ways:

1.Check you image resolution which you are using in you code, use required image resolution only.

Suppose you are using 40x40 image size for 1X, 80x80 in 2x so on.. Then use only required resolution not more then that.

Check you are using updated Lazy loading library. Use Memory Allocation tool and check VM allocation, if you got any IMAGEIO regarding issue then defiantly you have contained high resolution image in you NSMainBundle.

Resize high resolution image in required resolution and use imageWithContentsOfFile instead of imageName.

Upvotes: 0

Marcus S. Zarra
Marcus S. Zarra

Reputation: 46718

You need to reset the Core Data object otherwise it will retain the binary data. This is one of many reasons why it is not a good idea to store binary data in Core Data. You are better off storing the images on disk and keep just the file pointer in Core Data itself.

To reset the NSManagedObject you need to call:

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

That will turn the object into a fault and all of its values will be dropped from memory.

You can also force a context wide refresh by calling -reset on the NSManagedObjectContext.

Upvotes: 5

Related Questions