Сбой NSPersistentDocument при «сохранить как»

Приложение My Core Data, основанное на документах, аварийно завершает работу при «сохранить как». Проблема похожа на ту, что описана в теме какао-dev под названием "Объекты NSPersistentDocument "выпотрошены" после дублирования, переименования в 10.9"

Ключевые отличия заключаются в том, что:

  • Я ориентируюсь и работаю на OS X 10.10 Yosemite
  • Используйте «Сохранить как», а не дублировать
  • Сбой происходит раньше. Во время сохранения MOC

Проблема затрагивает даже самый простой NSPersistentDocument. Он существует по крайней мере с 2014 года. Поэтому я надеюсь, что другие столкнулись с той же проблемой и у вас есть обходной путь, которым вы хотите поделиться.

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

Шаги для воспроизведения сбоя:

  1. Создавайте и запускайте на Yosemite. Ошибка, похоже, была исправлена ​​​​в El Capitan.
  2. Создать новый документ
  3. Вставить новый объект
  4. Сохраните документ
  5. Закрыть документ
  6. Повторно открыть документ
  7. Изменить значение атрибута в таблице
  8. Используйте «Сохранить как», чтобы сохранить под новым именем

В OS X Yosemite всегда происходит сбой со следующей трассировкой:

_propertyAtIndexForEntityDescription ()
snapshot_get_value_as_object ()
-[NSManagedObject(_NSInternalMethods) _validatePropertiesWithError:] ()
-[NSManagedObject(_NSInternalMethods) _validateForSave:] ()
-[NSManagedObject validateForUpdate:] ()
-[NSManagedObjectContext(_NSInternalAdditions) _validateObjects:forOperation:error:exhaustive:forSave:] ()
-[NSManagedObjectContext(_NSInternalAdditions) _validateChangesForSave:] ()
-[NSManagedObjectContext(_NSInternalChangeProcessing) _prepareForPushChanges:] ()
-[NSManagedObjectContext save:] ()
-[NSPersistentDocument writeToURL:ofType:forSaveOperation:originalContentsURL:error:] ()
-[NSDocument _writeSafelyToURL:ofType:forSaveOperation:forceTemporaryDirectory:error:] ()
-[NSDocument _writeSafelyToURL:ofType:forSaveOperation:error:] ()
-[NSDocument writeSafelyToURL:ofType:forSaveOperation:error:] ()
-[NSPersistentDocument writeSafelyToURL:ofType:forSaveOperation:error:] ()
__66-[NSDocument saveToURL:ofType:forSaveOperation:completionHandler:]_block_invoke_22353 ()
__66-[NSDocument saveToURL:ofType:forSaveOperation:completionHandler:]_block_invoke2350 ()
__66-[NSDocument saveToURL:ofType:forSaveOperation:completionHandler:]_block_invoke_22222 ()
__110-[NSFileCoordinator(NSPrivate) _coordinateReadingItemAtURL:options:writingItemAtURL:options:error:byAccessor:]_block_invoke428 ()
-[NSFileCoordinator(NSPrivate) _invokeAccessor:orDont:andRelinquishAccessClaim:] ()
-[NSFileCoordinator(NSPrivate) _coordinateReadingItemAtURL:options:writingItemAtURL:options:error:byAccessor:] ()
-[NSDocument _fileCoordinator:coordinateReadingContentsAndWritingItemAtURL:byAccessor:] ()
-[NSDocument _fileCoordinator:asynchronouslyCoordinateReadingContentsAndWritingItemAtURL:byAccessor:] ()
__66-[NSDocument saveToURL:ofType:forSaveOperation:completionHandler:]_block_invoke2221 ()
-[NSDocument _prepareToSaveToURL:forSaveOperation:completionHandler:] ()
__66-[NSDocument saveToURL:ofType:forSaveOperation:completionHandler:]_block_invoke ()
-[NSDocument continueFileAccessUsingBlock:] ()
-[NSDocument _performFileAccessOnMainThread:usingBlock:] ()
-[NSDocument performAsynchronousFileAccessUsingBlock:] ()
-[NSDocument saveToURL:ofType:forSaveOperation:completionHandler:] ()
__85-[NSDocument saveToURL:ofType:forSaveOperation:delegate:didSaveSelector:contextInfo:]_block_invoke_2 ()
-[NSDocument _commitEditingThenContinue:] ()
__62-[NSPersistentDocument _documentEditor:didCommit:withContext:]_block_invoke ()

Изменить 1. Возможный обходной путь:

Я могу устранить сбой, предотвратив сохранение исходного контекста управляемого объекта во время операции «сохранить как». После «сохранить как» я сразу же закрываю существующий документ и снова открываю документ из нового места. Все это очень уродливо и может сломать другое поведение NSPersistentDocument.

Редактировать 2. Приведенный выше обходной путь теряет несохраненные изменения

Предотвращение сохранения исходного контекста управляемого объекта позволяет избежать сбоя. Однако конечным результатом является копия документа в его последнем сохраненном состоянии. Несохраненные изменения теряются.

Редактировать 3. Выпотрошенный снимок

К тому времени, когда старый контекст управляемого объекта попытается сохранить изменения в новом файле, моментальный снимок объекта уже не знает свою сущность <_CDSnapshot_Entity_: 0x600001f3cfd0> (entity: (null); id: 0x40000b <x-coredata://83B64FD3-B5B9-44CB-976D-54C0326FDFF5/Entity/p1> ; data: (null)). Я не вижу никакой переменной экземпляра, поддерживающей -[_CDSnapshot entity]. Я предполагаю, что он должен найти это по идентификатору объекта.


person Pierre Bernard    schedule 19.04.2016    source источник
comment
Теперь это становится странным: пример проекта падает на одной машине El Capitan, но не на другой. На машине, где пример приложения не дает сбоев, производственный процесс создает пустой документ во время сохранения как.   -  person Pierre Bernard    schedule 19.04.2016
comment
Я вижу, что сохранение вызывает публичный метод validateForUpdate:. Интересно, что произойдет, если вы вызовете это непосредственно на объекте документа.   -  person Phillip Mills    schedule 19.04.2016
comment
Нет сбоя, если я позвоню validateForUpdate: перед вызовом [super writeToURL:ofType:forSaveOperation:originalContentsURL: error:]. Я думаю, что проблема возникает, когда существующий контекст управляемого объекта вызывается при записи в новое место. Похоже, что свойства управляемых объектов больше не доступны в этот момент.   -  person Pierre Bernard    schedule 19.04.2016
comment
Я добавил точки останова и ведение журнала для различных контрольных точек в подклассе NSManagedObject. Я обнаружил, что -[NSPersistentStoreCoordinator migratePersistentStore:toURL:options:withType:error:] создает новый экземпляр моего управляемого объекта в новом контексте управляемого объекта. Позже этот новый управляемый объект становится неисправным и освобождается. Затем -[NSManagedObjectContext save:] вызывается validateForSave: для исходного управляемого объекта.   -  person Pierre Bernard    schedule 19.04.2016
comment
Прежде чем -[super writeToURL:ofType:forSaveOperation:originalContentsURL:error:]контекст управляемого объекта все еще был связан со старым URL-адресом файла. Доступ к атрибутам управляемых объектов не вызывает проблем. В -[NSManagedObject save] контекст управляемого объекта по-прежнему связан с новым URL-адресом файла. Доступ к атрибутам или попытки обновить объекты завершаются неудачей. Я думаю, что снимок исчез.   -  person Pierre Bernard    schedule 19.04.2016


Ответы (1)


Я придумал обходной путь, который, кажется, работает для моего варианта использования.

- (BOOL)writeToURL:(NSURL *)absoluteURL
            ofType:(NSString *)typeName
  forSaveOperation:(NSSaveOperationType)saveOperation
originalContentsURL:(NSURL *)absoluteOriginalContentsURL
             error:(NSError **)error
{
    if ((saveOperation == NSSaveAsOperation) && (absoluteOriginalContentsURL != nil)) {
        NSFileManager *fileManager = [NSFileManager defaultManager];

        if (![fileManager copyItemAtURL:absoluteOriginalContentsURL toURL:absoluteURL error:error]) {
            return NO;
        }

        NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
        NSPersistentStoreCoordinator *persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator;
        NSPersistentStore *store = [persistentStoreCoordinator persistentStoreForURL:absoluteOriginalContentsURL];

        [persistentStoreCoordinator setURL:absoluteURL forPersistentStore:store];

        if (![managedObjectContext save:error]) {
            return NO;
        }

        return YES;
    }

    return [super writeToURL:absoluteURL
                             ofType:typeName
                   forSaveOperation:saveOperation
                originalContentsURL:absoluteOriginalContentsURL
                              error:error];
}

При сохранении как я копирую старый документ в новое (временное) место. Затем я устанавливаю новый URL-адрес в постоянном хранилище и позволяю контексту управляемого объекта сохранять отложенные изменения в этом новом документе.

NSPersistentDocument заботится о создании временного URL-адреса, переданного как absoluteURL, перемещает сохраненный файл в новое место и вызывает setFileURL: после завершения сохранения.

Я отключил ведение журнала в хранилище SQLite, поддерживающем документ. Таким образом, мне нужно скопировать только один файл в absoluteOriginalContentsURL.

person Pierre Bernard    schedule 27.04.2016