Почему NSManagedObjectContextObjectsDidChangeNotification вызывается дважды с удалёнными объектами после откат?

Я использую NSManagedObjectContextObjectsDidChangeNotification, и у меня возникла проблема, когда после вставки объекта и последующего вызова отката, чтобы он был удален, уведомление об изменении с удаленным объектом вызывается дважды. Это неожиданное уведомление вызывает осложнения по линии, которую я проследил по этой проблеме. Я продемонстрировал пример Apple Earthquakes, чтобы продемонстрировать эту проблему. Редактирование:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self reloadTableView:self];

    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(contextObjectsDidChangeNotification:)
     name:NSManagedObjectContextObjectsDidChangeNotification
     object:self.managedObjectContext];

    AAPLQuake* quake = (AAPLQuake *)[NSEntityDescription insertNewObjectForEntityForName:@"Quake" inManagedObjectContext:self.managedObjectContext];

    NSLog(@"Why is deleted notified twice?");
    [self.managedObjectContext rollback];
}

-(void)contextObjectsDidChangeNotification:(NSNotification*)notify{
    NSLog(@"contextObjectsDidChangeNotification:");
    NSDictionary* userInfo = notify.userInfo;
    NSSet* inserted = userInfo[NSInsertedObjectsKey];
    if(inserted){
        NSLog(@"\tinserted %ld", inserted.count);
    }
    NSSet* deleted = userInfo[NSDeletedObjectsKey];
    if(deleted){
        NSLog(@"\tdeleted %ld", deleted.count);
    }
}

Выполнение этого результата приводит к следующему выводу:

2015-12-23 00:15:20.086 Earthquakes[7631:5431685] Why is deleted called twice?
2015-12-23 00:15:20.086 Earthquakes[7631:5431685] contextObjectsDidChangeNotification:
2015-12-23 00:15:20.086 Earthquakes[7631:5431685]   inserted 1
2015-12-23 00:15:20.086 Earthquakes[7631:5431685] contextObjectsDidChangeNotification:
2015-12-23 00:15:20.087 Earthquakes[7631:5431685]   deleted 1
2015-12-23 00:15:20.087 Earthquakes[7631:5431685] contextObjectsDidChangeNotification:
2015-12-23 00:15:20.087 Earthquakes[7631:5431685]   deleted 1

Пример доступного проекта здесь.

Кто-нибудь знает, почему это произойдет? Я испытываю это как на OS X 10.11.2, так и на iOS 9.2.

Ответ 1

Я вижу такое же поведение, и я не знаю, почему это происходит. Однако кажется, что вы можете отличить первый вызов от второго, посмотрев на свойство hasChanges управляемого объекта. При первом вызове hasChanges есть ДА, а во втором вызове НЕТ. Этого должно быть, по крайней мере, достаточно, чтобы не дважды обрабатывать удаление.

То же самое происходит и с объектным свойством deleted, поэтому лучше проверить оба.

Ответ 2

rollback выполняет вызов processPendingChanges, и первое уведомление является результатом удаляемого объекта и, как представляется, имеет все остальные значения, как ожидалось (inserted и deleted оба значения true).

Второе уведомление является результатом второго вызова processPendingChanges в реализации rollback. Обратите внимание, что на этот раз он не помечен как deleted.

Сначала я думал, что это может быть связано с распространением делеции, но второе уведомление происходит, даже если для параметра propagatesDeletesAtEndOfEvent установлено значение NO.

Следующая трассировка относится только к методу rollback (я удалял все вызовы методов, кроме тех, что на NSManagedObjectContext, чтобы он был коротким - я также удалял вызовы для сохранения/выпуска).

Вероятно, есть веская причина сделать второй вызов processPendingChanges, потому что там происходит довольно много работы (см. конец этого сообщения).

Если бы я должен был предположить, я бы сказал, что есть ошибка в реализации отката, где удаленные объекты не удаляются из данных для второго уведомления.

Я рекомендую два курса. Сначала напишите отчет об ошибке с помощью apple. Во-вторых, при обработке событий изменения объектов проверяйте атрибут deleted объектов в удаленном наборе уведомления и игнорируйте те, которые не помечены как deleted.

Вот трассировка...

- NSManagedObjectContext NSManagedObjectContext rollback
- NSManagedObjectContext NSManagedObjectContext discardEditing
- NSManagedObjectContext NSManagedObjectContext isEditing
- NSManagedObjectContext NSManagedObjectContext propagatesDeletesAtEndOfEvent
- NSManagedObjectContext NSManagedObjectContext setPropagatesDeletesAtEndOfEvent:
- NSManagedObjectContext NSManagedObjectContext performBlockAndWait:
- NSManagedObjectContext NSManagedObjectContext processPendingChanges
- NSManagedObjectContext NSManagedObjectContext _processRecentChanges:
- NSManagedObjectContext NSManagedObjectContext _postRefreshedObjectsNotificationAndClearList
- NSManagedObjectContext NSManagedObjectContext _processReferenceQueue:
- NSManagedObjectContext NSManagedObjectContext deleteObject:
- NSManagedObjectContext NSManagedObjectContext _registerClearStateWithUndoManager
- NSManagedObjectContext NSManagedObjectContext _establishEventSnapshotsForObject:
- NSManagedObjectContext NSManagedObjectContext _enqueueEndOfEventNotification
- NSManagedObjectContext NSManagedObjectContext _postObjectsDidChangeNotificationWithUserInfo:
- NSManagedObjectContext NSManagedObjectContext processPendingChanges
- NSManagedObjectContext NSManagedObjectContext _processRecentChanges:
- NSManagedObjectContext NSManagedObjectContext _registerClearStateWithUndoManager
- NSManagedObjectContext NSManagedObjectContext _updateUnprocessedOwnDestinations:
- NSManagedObjectContext NSManagedObjectContext _propagatePendingDeletesAtEndOfEvent:
- NSManagedObjectContext NSManagedObjectContext _processPendingDeletions:withInsertions:withUpdates:withNewlyForgottenList:withRemovedChangedObjects:
- NSManagedObjectContext NSManagedObjectContext _processPendingInsertions:withDeletions:withUpdates:
- NSManagedObjectContext NSManagedObjectContext _processPendingUpdates:
- NSManagedObjectContext NSManagedObjectContext _registerUndoForModifiedObjects:
- NSManagedObjectContext NSManagedObjectContext _registerUndoForInsertedObjects:
- NSManagedObjectContext NSManagedObjectContext _registerUndoForDeletedObjects:withDeletedChanges:
- NSManagedObjectContext NSManagedObjectContext _registerUndoForOperation:withObjects:withExtraArguments:
- NSManagedObjectContext NSManagedObjectContext _updateUndoTransactionForThisEvent:withDeletions:withUpdates:
- NSManagedObjectContext NSManagedObjectContext _clearRefreshedObjects
- NSManagedObjectContext NSManagedObjectContext _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:
- NSManagedObjectContext NSManagedObjectContext _postObjectsDidChangeNotificationWithUserInfo:
- NSManagedObjectContext NSManagedObjectContext _processRecentlyForgottenObjects:
- NSManagedObjectContext NSManagedObjectContext _forgetObject:propagateToObjectStore:
- NSManagedObjectContext NSManagedObjectContext _forgetObject:propagateToObjectStore:removeFromRegistry:
- NSManagedObjectContext NSManagedObjectContext _processReferenceQueue:
- NSManagedObjectContext NSManagedObjectContext _isDeallocating
- NSManagedObjectContext NSManagedObjectContext _forgetObject:propagateToObjectStore:
- NSManagedObjectContext NSManagedObjectContext _forgetObject:propagateToObjectStore:removeFromRegistry:
- NSManagedObjectContext NSManagedObjectContext _resetAllChanges
- NSManagedObjectContext NSManagedObjectContext _clearUnprocessedUpdates
- NSManagedObjectContext NSManagedObjectContext _clearUpdates
- NSManagedObjectContext NSManagedObjectContext _clearUnprocessedInsertions
- NSManagedObjectContext NSManagedObjectContext _clearInsertions
- NSManagedObjectContext NSManagedObjectContext _clearUnprocessedDeletions
- NSManagedObjectContext NSManagedObjectContext _clearDeletions
- NSManagedObjectContext NSManagedObjectContext _clearLockedObjects
- NSManagedObjectContext NSManagedObjectContext _clearRefreshedObjects
- NSManagedObjectContext NSManagedObjectContext _incrementUndoTransactionID
- NSManagedObjectContext NSObject willChangeValueForKey:
- NSManagedObjectContext NSObject observationInfo
- NSManagedObjectContext NSObject _implicitObservationInfo
- NSManagedObjectContext NSObject didChangeValueForKey:
- NSManagedObjectContext NSObject _pendingChangeNotificationsArrayForKey:create:
- NSManagedObjectContext NSManagedObjectContext setPropagatesDeletesAtEndOfEvent:
- NSManagedObjectContext NSManagedObjectContext performBlockAndWait: