سؤال

In my application I'm using CoreData for storage and displaying data using NSFetchedResultsController.

I followed tutorials from raywenderlich to get it done and it's a lot of code - but it's working properly in general - will post parts of it when it's needed. I stuck on one problem which I cannot understand.

Data which is displayed inside a UITableView combined with NSFetchedResultsController can be updated in background - and here is where my problem started.

I'm doing Pull-to-refresh approach and starting a download in background on separate thread. It's using it's own NSManagedObjectContext created for this thread and saving it after all objects are created.

Here is code:

- (void)refresh:(id)sender
{
[ServerConnection downloadContactsForToken:token success:^(NSDictionary* responseObject)
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSManagedObjectContext* context = [[[AppManager sharedAppManager] createManagedObjectContext] retain];

            NSArray* responseContacts = responseObject[@"contacts"];
            [responseContacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                //ContactDB is NSManagedObject
                [[[ContactDB alloc] initWithDictionary:obj andContext:context] autorelease];
            }];

            [context save:nil];
            [context release];

            dispatch_async(dispatch_get_main_queue(), ^{
                [self.refreshControl endRefreshing];
            });
        });
    }];
}

According to what I've read in Apple docs the proper way to detect those changes on main thread NSManagedObjectContext is this:

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

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

- (void)managedObjectContextDidSave:(NSNotification *)notification {
    dispatch_async(dispatch_get_main_queue(), ^{
        if(notification.object != [[AppManager sharedAppManager] managedObjectContext]) {
            [[[AppManager sharedAppManager] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
        }
    });
}

Basically when I get notification about changes in any managedObjectContext I merge changes to main thread context. And it works in general, but after I started profiling I discovered that all objects that are merged into described process are never deallocated.

When I do everything on main thread - it works - they get deallocated as expected when I scroll UITableView.

I found a workaround and I'm calling:

[[[AppManager sharedAppManager] managedObjectContext] reset];

After merge is done:

[[[AppManager sharedAppManager] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
[[[AppManager sharedAppManager] managedObjectContext] reset];

But I have no idea why I have to do it and if that's gonna break something else. Maybe there is a better way to refresh data in background and I'm on completely wrong track.

هل كانت مفيدة؟

المحلول

In general, you shouldn’t do anything special with managed objects except normal Foundation memory management rules (reference counting). So just make sure you don’t retain them anywhere when you don’t need them.

Turning objects bank into faults using -refreshObject:mergeChanges: is only needed when you need to partially trim the object graph and still have strong references to objects.

And I noticed a couple of other things in your code.

You’re subscribing to all context-did-save notifications. This is dangerous because you can receive those notifications for the contexts that you don’t own. From example, from the Address Book or from some third-party library you’re using.

In the network operation completion handler you’re dispatching work to the global concurrent queue and create a new context from there. By using global concurrent queue you’re not controlling the number of concurrent tasks. It is possible that to a) run out of threads, and b) create many new contexts that would compete for one persistent store coordinator and for one persistent store. I would suggest dispatching to a serial queue or using a context with private queue concurrency type that manages private serial queue.

نصائح أخرى

This is caused by retain cycles. Very common when dealing with managed objects. See Core Data Programming Guide: Memory Management (Breaking Relationship Retain Cycles).

When you have relationships between managed objects, each object maintains a strong reference to the object or objects to which it is related. In a managed memory environment, this causes retain cycles (see Object Ownership and Disposal) that can prevent deallocation of unwanted objects. To ensure that retain cycles are broken, when you're finished with an object you can use the managed object context method refreshObject:mergeChanges: to turn it into a fault.

You typically use refreshObject:mergeChanges: to refresh a managed object's property values. If the mergeChanges flag is YES, the method merges the object's property values with those of the object available in the persistent store coordinator. If the flag is NO, however, the method simply turns an object back into a fault without merging, which causes it to release related managed objects. This breaks the retain cycle between that managed object and the other managed objects it had retained.

Also note a context keeps strong references to managed objects that have pending changes (insertions, deletions, or updates) until the context is sent a save:, reset , rollback, or dealloc message, or the appropriate number of undos to undo the change.

In addition, Core Data has a concept called a "User Event." By default, a "User Event" is properly wrapped in the main run loop, however for MOCs not on the main thread, you are responsible for making sure user events are properly processed. See Use Thread Confinement to Support Concurrency and Track Changes in Other Threads Using Notifications.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top