Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS Google Analytics memory growing out of control FAST

I've used Google Analytics on several iOS apps. No problems. This time, problem.

I do the basic setup using version 3.0. Add library/header, include required frameworks, and stuff the boiler plate code into the AppDelegate.m. So far so good, everything works as expected. I take my first UIViewController and change it to extend GAITrackedViewController and it hits the fan. The app freezes up on the first screen and memory usage starts going up about 4Meg per second. So I change the UIViewController back and all is good. I try making the screen name call manually in viewDidLoad.

// Analytics
id tracker = [[GAI sharedInstance] defaultTracker];
[tracker set:kGAIScreenName value:@"Initial"];
[tracker send:[[GAIDictionaryBuilder createAppView] build]];

Same thing happens. My view controller has a couple custom container views and it the root view controller on a generic UINavigationViewController. I figure it's probably the custom containers confusing it about which is the active view controller and what screen name to use (but I'm not seeing any sign of this in the logging).

Has anyone run into this problem and been able to nail down exactly what's causing it and how to work around it?

like image 882
DBD Avatar asked Feb 28 '14 17:02

DBD


3 Answers

João's answer is correct, but I'd like to explain it more.

From Google's Getting Started document

If your app uses the CoreData framework: responding to a notification, e.g. NSManagedObjectContextDidSaveNotification, from the Google Analytics CoreData object may result in an exception. Instead, Apple recommends filtering CoreData notifications by specifying the managed object context as a parameter to your listener.

What that means is...

// This code will cause a problem because it gets triggered on ANY NSManagedObjectContextDidSaveNotification.
// (both your managed object contact and the one used by Google Analytics)
[[NSNotificationCenter defaultCenter] addObserver:self 
                                      selector:@selector(managedObjectContextDidSave:) 
                                      name:NSManagedObjectContextDidSaveNotification 
                                      object:nil];

// This code is safe and will only be trigger from the notification generated by your Managed Object Context.
[[NSNotificationCenter defaultCenter] addObserver:self 
                                      selector:@selector(managedObjectContextDidSave:) 
                                      name:NSManagedObjectContextDidSaveNotification 
                                      object:myManagedObjectContext];

Now I read the docs and I had done this properly, but I was still having the problem. Turns out I didn't update my code for when I removed the notification.

// Not Safe
[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:NSManagedObjectContextDidSaveNotification 
                                              object:nil];

// Safe
[[NSNotificationCenter defaultCenter] removeObserver:self 
                                                name:NSManagedObjectContextDidSaveNotification 
                                              object:myManagedObjectContext];

The moral of the story is, pay attention to your notification listeners. It takes a couple seconds to specify a listener for a specific object and it can take a long time to debug an issue because you accidentally listening to events you don't want to or remove listening to events.

like image 75
DBD Avatar answered Nov 13 '22 04:11

DBD


Solution 1: Use a specific moc on object parameter when observing NSManagedObjectContextDidSaveNotification, this will allow you to observe only saves on the given moc.

[[NSNotificationCenter defaultCenter] addObserver:self 
                                  selector:@selector(managedObjectContextDidSave:) 
                                  name:NSManagedObjectContextDidSaveNotification 
                                  object:managedObjectContext];

Solution 2: If you are using the Core Data technique of merging mocs created on background threads, you cannot easily solve in the suggested way, so the alternative is to change your method that handles notification in order to avoid merging when the persistentStoreCoordinator of the saved moc doesn't match the persistentStoreCoordinator of your main moc.

- (void)managedObjectContextDidSave:(NSNotification *)notification {
    if ([NSThread isMainThread]) {
        NSManagedObjectContext *savedMoc = notification.object;

        // Merge only saves of mocs that are not my managedObjectContext
        if (savedMoc == self.managedObjectContext) {
            return;
        }

        // Merge only saves of mocs that share the same persistentStoreCoordinator of my managedObjectContext (i.e.: ignore the save of Google Analytics moc)
        if (savedMoc.persistentStoreCoordinator != self.managedObjectContext.persistentStoreCoordinator) {
            return;
        }

        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }
    else {
        [self performSelectorOnMainThread:@selector(handleBackgroundContextSaveNotification:) withObject:notification waitUntilDone:YES];
    }
}
like image 34
Daniele Avatar answered Nov 13 '22 04:11

Daniele


I was having the exact same problem. I've managed to find the solution in my case: I was registering to NSManagedObjectContextDidSaveNotification without specifying the context:

[[NSNotificationCenter defaultCenter] addObserver:self 
                                      selector:@selector(managedObjectContextDidSave:) 
                                      name:NSManagedObjectContextDidSaveNotification 
                                      object:nil];

Removing this listener solved my out of memory problems.

Cheers

like image 40
João Correia Avatar answered Nov 13 '22 04:11

João Correia