Opitimizing my app – Story of an epic fail…

Ok, so I have a pretty heavy app that does a sync process with a webservice to get a complete content of the app. It is going to be an Enterprise app, view products, order products, etc. The complete sync process (only the first one) takes around 30-40 minutes, lots of images, lots of data… I was getting Memory warnings at the end of the sync, of course it had to be at the end of the sync…. I have various sync services running, to do a propper analysis I isolated just one sync process.


So I am having a nice heap growth of 1.08MB. Trying to find out what that is. I am doing a sync process over and over again with a json file of 41KB and downloading 130 images, 39 between 5-8KB and 97 from 60-400KBS

Begin status – Heap Growth of 1.08MB on each


Screen Shot 2013-08-24 at 10.43.25

Following the highest percentage on the Call Trees view, I see many things but the first one I find strange is the 4.6% for getting the path to the document directory.

Screen Shot 2013-08-24 at 10.41.43
This is the evil code:

- (NSString *)pathInDocumentDirectory:(NSString *)fileName
NSArray *documentDirectories =
NSUserDomainMask, YES);

NSString *baseDocPath = [documentDirectories objectAtIndex:0];

// Append passed in file name to that directory, return it
return [baseDocPath stringByAppendingPathComponent:fileName];

I am going to try out saving the baseDocPath to an instance variable to see how this affects. Tried out code:

- (NSString *)pathInDocumentDirectory:(NSString *)fileName
if (baseDocPath == nil) {
NSArray *documentDirectories =
NSUserDomainMask, YES);

baseDocPath = [documentDirectories objectAtIndex:0];

return [baseDocPath stringByAppendingPathComponent:fileName];

–> Heap went down almost 100KB (1.08MB to 909KB)
Screen Shot 2013-08-24 at 10.43.25

The call tree below makes me think that the NSManagedObjectContext fetch request stores a cache. But I do reset the NSManagedObjectContext each time I begin a new Sync, I have also tried to reset it each time I finish a sync. But this doesn’t make any difference.

–> No result

Right now I am leaving the app open to see if at some point the memory flushes. Even a simulation of a memory warning doesn’t decrease the memory usage.

Trying to refreshObject:mergeChanges:YES once the image of the correspondant Entity is not needed any more

–> No result

Trying to refreshObject:mergeChanges:NO

Source: stackoverflow

Apple docs

Finally, Core Data does not by default keep strong references to managed objects (unless they have unsaved changes). If you have lots of objects in memory, you should determine the owning references. Managed objects maintain strong references to each other through relationships, which can easily create strong reference cycles. You can break cycles by re-faulting objects (again by using the refreshObject:mergeChanges: method of NSManagedObjectContext).

–> No result

[self.context setStalenessInterval:0.0];
Source apple developer:

Sets the maximum length of time that may have elapsed since the store previously fetched data before fulfilling a fault issues a new fetch rather than using the previously-fetched data.
The maximum length of time that may have elapsed since the store previously fetched data before fulfilling a fault issues a new fetch rather than using the previously-fetched data.
A negative value represents an infinite value; 0.0 represents “no staleness acceptable”.

–> This increased a little bit the memory

Following guidelines:
Source apple developer

NSString *predicateString = [NSString stringWithFormat @"employeeID == $EMPLOYEE_ID"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];

for (NSString *anID in employeeIDs) {
NSDictionary *variables = @{ @"EMPLOYEE_ID" : anID };
NSPredicate *localPredicate = [predicate predicateWithSubstitutionVariables:variables];

–> Won: 30KB
Screen Shot 2013-08-25 at 10.43.29


Replacing the stupid way to check the type of the incomming file:


//    NSMutableSet * matches = [NSMutableSet setWithSet:[CDAFileHelper videoFileTypeExtensions]];

//    NSPredicate *p = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",pathExtension];

//    [matches filterUsingPredicate:p];

–> Win: 175KB!!


Screen Shot 2013-08-25 at 11.00.25


Replaced calculating always the cache directory by storing it in a instance variable

return [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];

–> Win: 7-8KB

Screen Shot 2013-08-25 at 11.06.13


stupidly I was first serializing the json file to store it and then read it in again… removed the storing and just parsing the serialized json file:

–> Win: 72KB

Screen Shot 2013-08-25 at 11.40.36


Well… after many many hours…. an epifani… lesson learned


It fucks everything up…

My new lovely heap growth after using Allocations instrument…

Screen Shot 2013-08-25 at 12.53.16

Apple tip on zombies instruments

Tip: The Zombies template causes memory growth because the zombies are never deallocated. So for iOS apps, use it with iOS Simulator rather than on the device itself. For the same reason, don’t use the Zombies template concurrently with the Leaks instrument.

Should this really be a “Tip” or a whole “Warning”!! I actually read it before… that is why I was using the simulator… but somehow I didn’t realize what the “causes memory growth” meant for my abandoned memory analysis


The memory issue was coming from somewhere else, acutally other Sync process. What helped me there, to not having memory warning issues, was to find the correct places, in specific batches, where to save the Core Data context.

Leave a Reply

Your email address will not be published. Required fields are marked *