Fix random mergeChangesFromContextDidSaveNotification: crashes

Getting crashes when merging NSManagedObjectContexts?

1. Clean up leaked observers. We were getting a really random crash trying to merge contexts between threads. Turns out we forgot to clean up an observer on one of our views that was using a NSManagedObject being merged. When the object changed, the observer fired on a deallocated view. Enables zombies to find the culprit.

2. Merge on the proper thread. If you must use multiple contexts, the recommended configuration is one context per-thread. When interacting with a context, you must make sure to do so on the proper thread. Block example:

    - (void)contextDidSaveNotification:(NSNotification*)saveNotification {
        [self.mergeContext performBlock:^{
            [self.mergeContext mergeChangesFromContextDidSaveNotification:notification];
        }];
     }

3. Fault in updated objects. This is a last resort type of thing but it’s possible the object for some reason doesn’t exist in the context we’re merging to yet (even though it should). I found that faulting in updated objects before merging fixed this issue before. http://www.mlsite.net/blog/?p=518

4. Avoid manipulating an object across contexts simultaneously. I’ve seen it cause crashes (awakeFromSnapshotEvents: message sent to deallocated instance), especially when using nested contexts.

Advertisements

How to observe a collection of objects and changes to their properties

The easy way is to use an NSArrayController and add an observer like so:

[friendsArrayController addObserver:self forKeyPath:@"arrangedObjects.name" options:0 context:nil];

The hard way is to observe a model’s collection property and add/remove observers when objects get added/removed from the collection like so:

[person addObserver:self forKeyPath:@"friends" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (object == person && [keyPath isEqualTo:@"friends"]) {
            if ([[change valueForKey:NSKeyValueChangeKindKey] intValue] == NSKeyValueChangeInsertion) {
                for (Friend *newFriend in [change valueForKey:NSKeyValueChangeNewKey]) {
                    if (![observedFriends containsObject:newFriend]) {
                        [newFriend addObserver:self forKeyPath:@"name" options:0 context:nil];
                    }
                }
            }
            else if ([[change valueForKey:NSKeyValueChangeKindKey] intValue] == NSKeyValueChangeRemoval) {
                for (Friend *oldFriend in [change valueForKey:NSKeyValueChangeOldKey]) {
                    if (![observedFriends containsObject:oldFriend]) {
                        [oldFriend removeObserver:self forKeyPath:@"name"];
                    }
                }
            }
            else if ([[change valueForKey:NSKeyValueChangeKindKey] intValue] == NSKeyValueChangeReplacement) {
                for (Friend *newFriend in [change valueForKey:NSKeyValueChangeNewKey]) {
                    if (![observedFriends containsObject:newFriend]) {
                        [newFriend addObserver:self forKeyPath:@"name" options:0 context:nil];
                    }
                }
                for (Friend *oldFriend in [change valueForKey:NSKeyValueChangeOldKey]) {
                    if (![observedFriends containsObject:oldFriend]) {
                        [oldFriend removeObserver:self forKeyPath:@"name"];
                    }
                }
            }
    }
}