Reputation: 3198
My Core Data document-based application crashes on "save as". The problem seems similar to the one described in the cocoa-dev thread titled "NSPersistentDocument objects "gutted" after Duplicate, Rename in 10.9"
The key differences being that:
The problem affects even the simplest NSPersistentDocument. It has been around at least since 2014. Thus I hope for others to have encountered the same problem and have a workaround you care to share.
My sample project uses a single entity with a single attribute. It has a table view to display all instances of the entity and a button to create a new one. I strayed from the default template only to disable autosavesInPlace.
The steps to reproduce the crash are:
On OS X Yosemite this always crashes with the following backtrace:
_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 ()
Edit 1. Possible workaround:
I can patch around the crash by preventing the original managed object context from being saved during the “save as” operation. After “save as” I immediately close the existing document and re-open the document from the new location. It’s all very ugly and may break other NSPersistentDocument behavior.
Edit 2. Above workaround loses unsaved changes
Preventing the original managed object context from saving does avoid the crash. The end-result however is a copy of the document in its last saved state. Unsaved changes are lost.
Edit 3. Gutted snapshot
By the time the old managed object context tries to save changes to the new file, the object snapshot no longer knows its entity <_CDSnapshot_Entity_: 0x600001f3cfd0> (entity: (null); id: 0x40000b <x-coredata://83B64FD3-B5B9-44CB-976D-54C0326FDFF5/Entity/p1> ; data: (null))
. I don’t see any instance variable backing -[_CDSnapshot entity]
. I assume it should find that from the object ID.
Upvotes: 1
Views: 360
Reputation: 3198
I have come up with a workaround that appears to work for my use case.
- (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];
}
On save as, I copy the old document to the new (temporary) location. Then I set the new URL on the persistent store and let the managed object context save pending changes to that new document.
NSPersistentDocument takes care of generating the temporary URL passed in as absoluteURL
, moves the saved file to the new location and calls setFileURL:
once saving is complete.
I have disabled journaling on the SQLite store backing the document. Thus I need only copy the one file at absoluteOriginalContentsURL
.
Upvotes: 1