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.

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.


The mysterious case of an unexpectedly changing NSString attribute

So I had a vanilla Core Data entity with a @property (nonatomic, retain) NSString *text property and a NSTextView *textView used to edit text that would be stored to this value. Initialization looked something like textView.string = model.text and saving looks like model.text = textView.string in textDidEndEditing:.

All good right? Wrong. The value wasn’t sticking. It was changed even though the model’s setter without ever being called. Can you guess why?

It took me wayy too long to debug, but finally… checking out NSText’s string getter method, we see:

For performance reasons, this method returns the current backing store of the text object. If you want to maintain a snapshot of this as you manipulate the text storage, you should make a copy of the appropriate substring.

Tracing NSLog(@"%@", [textView.string class]) we see it is actually an instance of NSBigMutableString. This means both the text editor and entity now hold a reference to the same mutable NSString. Basic CS101, duh! There are two possible fixes: Define the string property using copy vs retain, @property (nonatomic, copy) NSString *text or manually copy the string when setting the value like model.text = [textView.string copy]. See this Stack Overflow post for more info on when to use retain/copy on NSString.