NSPersistentDocument crash on “save as”

Hi!


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"

http://lists.apple.com/archives/cocoa-dev/2014/Mar/msg00119.html


The key differences being that:

- I target OS X 10.10 Yosemite

- Use "Save As" rather than Duplicate

- The crash occurs earlier. During the MOC save


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.

Do you have a workaround you care to share?


I have created a sample project:

https://dl.dropboxusercontent.com/u/2381634/DTS/SaveAs.zip


The 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.


The steps to reproduce the crash are:

1. Create a new document

2. Insert a new object

3. Save the document

4. Close the document

5. Re-open the document

6. Change the value of the attribute in the table

7. Use "Save As" to save under a new name


The backtrace looks like this:


_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 ()


Pierre

Replies

This bug seems to have been fixed in El Capitan. Since I still need to support Yosemite, I'll have to continue searching for a workaround.

Now it's getting weird: The sample project crashes on one El Capitan machine, but not on another. The one where it crashes is running 10.11.5 beta.


The production application crashes on Yosemite and on 10.11.5 beta. On the machine where the sample application does not crash, the production creates an empty document during "save as".

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.

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;
  }


  BOOL result = [super writeToURL:absoluteURL
  ofType:typeName
    forSaveOperation:saveOperation
  originalContentsURL:absoluteOriginalContentsURL
   error:error];


  return result;
}


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
.