Archive

Monthly Archives: August 2013

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.

Begin:

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 =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
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 =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
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:

previous:

//    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

DO NOT ANALYZE HEAP GROWTH WITH ZOMBIES INSTRUMENTS!!!

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.

– Avoid Capturing JSValues -> mantain a strong reference -> Prefer passing as arguments

– Avoid Captuing JSContexts -> mantain a strong reference to the Global object -> Use +[JSContext currentContext] inside of the blog

 

– JavaScriptCore uses garbage collection -> All references in JS are strong -> It doesn’t matter if you create reference cycles as the GC can handle them

– Memory management is mostly automatic -> JSValue keeps things alive for you as long as you use JSValue

– But attention to:

-> String JS values in Objective-C objects as instance variables -> Use JSManagedValue for that, it is a garbage collected reference.

JSManagedValue by itself  is a weak reference to a JavaScript value.

addMangedReference:withOwner turns JSManagedVAlue into a “garbage collected” reference -> if JS can find the owner it keeps the reference alive otherwise it is released

-> Adding JS fields to Objective-C objects, fields that are added in JS but are not present on the JSExport object

 

Threading:

JSVirtualMachin -> JSVM

– JSVM can contain multipe JSContexts

– you can have multiple JSVM with different multiple JSContexts

– JSValues can be passed between JSContext in the same JSVM

– but can not be passed between different JSVM -> each JSVM has its own heap and GC

– JavaScriptCore API is thread safe

– Locking granularity is at level of the VM, JS can be executed in different threads on the same VM but when one thread is executing JS no other thread can execute JS at the same time on the VM

– Use separate JSVM for concurrency