0
votes

Since upgrading to XCode 6.3 and iOS 8.3 I'm getting a new error in my app, when merging NSManagedObjectContexts (MOCs) from the background to the main thread MOC.

My architecture has a primary MOC on the main thread, and creates several background MOCs on other threads that are temporary and just used to sync new server data. When those background MOCs save, I listen for the notification NSManagedObjectContextDidSaveNotification on the main thread and call mergeChangesFromContextDidSaveNotification on the main MOC.

This is per the documentation on threading with Core Data (see Core Data Concurrency).

This used to work fine, but as of this update started crashing with this error, which I can't trace back to a specific line of my code:

illegally invoked -perform* on dying NSManagedObjectContext at:
    (
    0   CoreData                            0x28a2f273 <redacted> + 118
    1   CoreData                            0x28a32f09 <redacted> + 192
    2   CoreFoundation                      0x28c64e09 <redacted> + 12
    3   CoreFoundation                      0x28bbf515 _CFXNotificationPost + 1784
    4   Foundation                          0x29920749 <redacted> + 72
    5   Foundation                          0x2992522f <redacted> + 30
    6   Foundation                          0x29986a93 <redacted> + 98
    7   Foundation                          0x299867f7 <redacted> + 82
    8   Foundation                          0x299dfbd5 <redacted> + 408
    9   CoreFoundation                      0x28c72fed <redacted> + 20
    10  CoreFoundation                      0x28c706ab <redacted> + 278
    11  CoreFoundation                      0x28c709ff <redacted> + 734
    12  CoreFoundation                      0x28bbd201 CFRunLoopRunSpecific + 476
    13  CoreFoundation                      0x28bbd013 CFRunLoopRunInMode + 106
    14  GraphicsServices                    0x30359201 GSEventRunModal + 136
    15  UIKit                               0x2c361a59 UIApplicationMain + 1440
    16  MyAppName                           0x000c95a7 main + 170
    17  libdyld.dylib                       0x376c1aaf <redacted> + 2
)

I've turned zombies on in XCode, looking for deallocated contexts, but haven't found any. The main-thread listener that calls merge looks like this:

- (void)coreDataDidSaveNotification:(NSNotification *)notification {
    NSManagedObjectContext *savedContext = (NSManagedObjectContext *)notification.object;

    if (savedContext != self.mainManagedObjectContext) {
        __weak typeof(self) weakSelf = self;
        [self.dispatch dispatchOnMainThread:^{
            NSLog(@"before merge");
            [weakSelf.mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
            NSLog(@"after merge");
        }];
    }
}

The log message "after merge" gets called before the crash happens, which is extra confusing. I've checked retainCount on the background context in the notification and it's always greater than zero when the merge is called, so it's not getting released at that point.

Any idea what is happening?

1
The retain count can never be zero. You can't use retain count to determine if an object has been deallocated. - bbum

1 Answers

2
votes

After much research, I've discovered that the background contexts are indeed getting released before the merge can complete. If I retain references to them the crash never happens.

It looks like the behavior of the mergeChangesFromContextDidSaveNotification method has changed. The notification holding the context used to be enough to retain it until the merge was completed.

I believe what is happening now is that the mergeChangesFromContextDidSaveNotification method now internally holds a weak reference, and internally dispatches the merge on the main thread again, enqueueing it for the next run loop, which is why I see the "after merge" log happen before the crash occurs. By the time the merge actually executes on the next run loop, I'd released my reference to the background MOC, and the notification had released when it went out of scope, and since the internal reference is likely now weak the background MOC was deallocated.