9
votes

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

2
Maybe Core Data frees memory on Memory Warning Level 2? Is It possible to produce low memory crash with your scenario? - brigadir
Is there any "magic method" to simulate a Memory Warning Level 2? Or "simply" I have to consume memory? - LombaX
I don't know any simulation method. You should run another "heavy" app (Appstore for example), keeping your app in background and tracking console log and Instruments memory chart. The level 2 warning will be mentioned in console - so you should look at memory chart at that moment. - brigadir
Mmmmhh...but If I keep the app in background, It will be freezed, so I won't receive messages from the system, isn't it? However I tried something else. I made a cycle that begun to waste memory (a simple array of casual NSData). I increased the total memory trying various amount, (tried much times, sometimes letting iOS to kill my app), but Core Data NEVER released cache :-/ (obviously tried on a device, not on the simulator) - LombaX
Mmmhh...at this link the docs says that the memory warning is not sent to sospended apps: developer.apple.com/library/ios/#DOCUMENTATION/iPhone/… "When the system dispatches a low-memory warning to your app, respond immediately. iOS notifies all running apps whenever the amount of free memory dips below a safe threshold. (It does not notify suspended apps." - LombaX

2 Answers

9
votes

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.

0
votes

I think you should load the less objects into memory in batch.

memory released by coredata happens behind the scenes and you don't have to program for it; the bad news is that it happens behind the scenes and thus can 'magically' chew up memory.

Ways around it are many; for example, use a predicate to only select the rows you absolutely must need; don't do a general call to fetch everything and then go through the list one by one. More than likely you will crash when you do the general call and CoreData attempts to load all objects.