NSPersistentDocument在“另存为”时崩溃

时间:2016-04-19 10:07:11

标签: macos cocoa core-data nsdocument nspersistentdocument

我的核心数据基于文档的应用程序崩溃"另存为"。 这个问题看起来类似于cocoa-dev主题中描述的问题" NSPersistentDocument objects "gutted" after Duplicate, Rename in 10.9"

主要区别在于:

  • 我的目标并在OS X 10.10 Yosemite上运行
  • 使用"另存为"而不是重复
  • 崩溃发生得更早。在MOC保存期间

该问题甚至影响最简单的NSPersistentDocument。它至少自2014年以来一直存在。因此,我希望其他人遇到同样的问题,并有一个你需要分享的解决方法。

我的sample project使用具有单个属性的单个实体。它有一个表视图,用于显示实体的所有实例,以及一个用于创建新实例的按钮。我偏离默认模板只是为了禁用autosavesInPlace。

重现崩溃的步骤是:

  1. 在Yosemite上建立并运行。该错误似乎已在El修复 Capitan
  2. 创建新文档
  3. 插入新对象
  4. 保存文档
  5. 关闭文档
  6. 重新打开文档
  7. 更改表格中属性的值
  8. 使用"另存为"以新名称保存
  9. 在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]的实例变量。我认为应该从对象ID中找到它。

1 个答案:

答案 0 :(得分: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负责生成以absoluteURL传入的临时网址,将保存的文件移至新位置,并在保存完成后调用setFileURL:

我在支持文档的SQLite商店上禁用了日记功能。因此,我只需要复制absoluteOriginalContentsURL处的一个文件。