Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data memory usage and Memory Warning

I have this issue. I have a database of images in Core Data. I fetch all images (about 80MB) and put in an NSMutableArray. The objects are correctly faulted:

NSArray *fetchResults = [self.managedObjectContext executeFetchRequest:request error:&error];
self.cache = [NSMutableArray arrayWithArray:fetchResults];
for (ImageCache *imageObject in self.cache) {
    NSLog(@"Is fault? %i", [imageObject isFault]);
}

Reading the log, I see that the objects are all correctly faulted However, using Instruments, I see that 80MB of memory are used. I think this is why Core Data caches it's results, and should free the memory when it's needed. But (and this is my "problem"), if I simulate a memory warning, nothing happens! The 80MB remains there.

Looking at instruments - allocations, the 80MB are used by many Malloc: (example)

Graph Category Live Bytes # Living # Transitory Overall Bytes # Overall # Allocations (Net / Overall) 0 Malloc 176,00 KB 8,59 MB 50 57 18,39 MB 107 %0.00, %0.00 0 Malloc 200,00 KB 8,20 MB 42 460 98,05 MB 502 %0.00, %0.04 0 Malloc 168,00 KB 7,05 MB 43 19 10,17 MB 62 %0.00, %0.00

This is a link to an image of the entire Call Tree: https://www.dropbox.com/s/du1b5a5wooif4w7/Call%20Tree.png

Any ideas? Thanks

like image 635
LombaX Avatar asked Sep 06 '12 07:09

LombaX


1 Answers

Ok, I've understood why it happens. When you make a fetch request for an entity, even if the faulting is enabled, ALL DATA of that entity are loaded into memory. Including big binary data. You can solve this using many methods:

1- setting this on your NSFetchRequest: [request setIncludesPropertyValues:NO]; setting NO, the data are not loaded into the cache immediately, but only upon request (when you access the property and the fault is fired) But this have a "problem". Even if you try to fault again the propery (because you don't need it immediately and want to free the memory, using [self.managedObjectContext refreshObject:object mergeChanges:NO];), the memory is not freed. The cache stay alive until the managedObjectContext is reset.

This is better:

2- you can split your data into separate entities. In my case I had only 2 properties: an url and image data. I splitted the data into 2 entities with a 1:1 relationship: imagecache and imagedata. Made a fetchRequest for all the row of the "imagecache" entity (with the url property), and like the previous solution no memory was cached. The propery imagecache.relationship.image was correctly faulted. Accessing this property caused the fault to fire and the cache to be filled. But in this case, doing [self.managedObjectContext refreshObject:object mergeChanges:NO]; on the "imagecache" object (the "father" object), resulted in immediately freeing the cache and the memory, faulting again imagecache.relationship.image property. Attention: Don't do on the "child" object, if you do [self.managedObjectContext refreshObject:object.relationship mergeChanges:NO], for some reason the cache is not freed. I think this is why you traverse the relationship.

3- I said this was mainly an academic question, the real "all day" solution (better performance and less headache) for this issues is to avoid saving big data inside core data database. You can save your data as files and store only a reference (filepath) or, with iOS 5 you have the possibility to set "use external storage" on any "Data" property inside your core data model. This will do all the work for you.

like image 134
LombaX Avatar answered Oct 15 '22 07:10

LombaX