views:

52

answers:

2

Here's a little background:

I'm storing pretty big images (900x1200 pixels) in Core Data. These are coming from the UIImagePickerView, and I crop and resize them into this reasonable size.

I'm using a pretty standard value transformer to convert between the UIImage and NSData (and back.)

The program works, but it ends up getting memory warnings and then gets terminated.

If I remove the line that writes out the image to Core Data, the problem goes away (but I need those images stored!)

When I run Instruments, "Allocations" shows an expected spike when the image picker returns the full resolution image and I resize it down. I go from 5 to about 22 megs and back to 5 after the transformation is complete.

Allocations never shows any more growth.

I DO have a roughly 200 byte leak that I'm almost positive is unrelated, but I mention it in full disclosure.

Now, when I use the Memory Monitor instrument, I see that my app's "Real Memory" is growing by 5 megs every time I save one of these images.

So, any help (or ideas) would be greatly appreciated!

Why does Allocations not show this growth, but Memory Monitor does? Is this a clue?

At first I thought it was because my value transformer was using an auto release pool that might not be getting drained, but then I realized that was when I READ the data, not write it.

Here's the pertinent code. Please let me know if you have ANY ideas on how I can debug this!

-Pete

Here's my value transformer: First the interface declaration:

@interface ImageToDataTransformer : NSValueTransformer { }
@end

And now the implementation:

@implementation ImageToDataTransformer


+ (BOOL)allowsReverseTransformation {
    return YES;
}

+ (Class)transformedValueClass {
    return [NSData class];
}

- (id)transformedValue:(id)value {
    NSData *data = UIImagePNGRepresentation(value);
    return data;
}

- (id)reverseTransformedValue:(id)value {
    UIImage *uiImage = [[UIImage alloc] initWithData:value];
    return [uiImage autorelease];
}

Here's the code where I write it out:

FieldCollectorAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

NSManagedObjectContext *context = [appDelegate managedObjectContext];

NSError *error;
NSManagedObject *theFieldObject=nil;

// Populate the bare necessities

theFieldObject = [NSEntityDescription insertNewObjectForEntityForName:@"FieldObject"
                                               inManagedObjectContext:context];

NSManagedObject *theImageObject =[NSEntityDescription insertNewObjectForEntityForName:@"ImageObject" inManagedObjectContext:context];

// Let's link up the two

[theImageObject setValue:theFieldObject forKey:@"fieldObject"];
[theFieldObject setValue:theImageObject forKey:@"photo"];


[theFieldObject setValue:image forKeyPath:@"photo.Photo"];

// Save the context

[context save:&error];  
+1  A: 

It doesn't solve the mystery but may solve your problem (unless you have some reason for absolutely requiring the use of Core Data in this way) but you should not be using Core Data. It just isn't designed to store such huge chunks of binary data. Instead store a reference to the data (i.e. a filename or URL) in Core Data and use NSData's dataWithContentsOfFile and writeToFile.

Adam Eberbach
Adam,You're probably correct that it wasn't designed to do this and that was Plan A until I read another thread that said the opposite.Regardless, in the few hours since I've posted this, I've figured out the problem! I'll hit the "Answer your question" button!
VTPete
Here's a follow-up for people in the same situation:I easily converted my program to read/write/delete images to the documents folder rather than core data. As expected, performance on the write is a bit faster and a bit slower on the uncached read. RAM usage is now fine.I'm a tad conflicted over this decision as keeping everything stored in one system is always better than mixing and matching. I'd love to hear some real facts about when to use (and not use) core data. For now I'll stick with files that I manage...
VTPete
use Core Data for everything but binary data. Store binary data on disk and reference it in Core Data. That is the proper way to use it on iOS because of the caching that is employed.
Marcus S. Zarra
VTPete I think that is right. And Marcus, I keep meaning to buy your book...
Adam Eberbach
+1  A: 

The problem is that Core Data caches a lot for performance's sake. I should have guessed this earlier on as I've written similar generic data storage algorithms. It also explains why I didn't see it allocated as my memory. When you send the save message to your managed object context, the data written is cached.

You can flag NSManagedObjects as "dirty" (faulted) by using the NSManagedObjectContext message "RefreshObject: mergeChanges:" Do this to the object directly after you send the context the save message.

By using "NO" as the second argument, you are saying, "You'll need to go back to disk to get it."

An poof, my memory usage issues are solved.

Don't you love it when the fix is one line of code?

-Pete

VTPete