Proper use of iCloud fallback stores - core-data

I am working on a lightweight wrapper for UIManagedDocument which manages the existence of multiple documents in a person's iCloud account. See: APManagedDocument
I am relying heavily on the use of fallback stores in iOS 7 and caring as little as possible about the current state of iCloud. Thus letting core data do what it does best in regards the fallback stores based on my understanding from the WWDC 2013 Video - Session 207 What’s New in Core Data and iCloud
First a quick overview of how my manager works:
I create all my UIManagedDocuments in the local sandbox and set the appropriate persistent store options to enable iCloud sync. I never move the UIManagedDocument package from the sandbox.
When I want to know what documents exist in the cloud I perform a metadata query.
When I want to open one of those documents I check to see if it exists in the local sandbox first and if not create it in the local sandbox.
(This requires that the app needs to wait for the notification corresponding to the Using local storage: 0 message.)
With this set up I never need to know if iCloud is enabled or logged in. I just work locally and let core data do its thing with iCloud.
So far everything is working great but I ran into a little pickle with the scenario where the user creates a new document prior to logging into iCloud and I am presented with the following issues:
I cannot perform a metadata query because there is no iCloud to query.
Because of 1 I have to fall back to doing an intelligent local scan looking for packages that have 'local/store/persistentStore' in their path and listing those as valid documents.
Later when the user logs in it was my understanding that core data would move my local store data to the cloud but I am not seeing that. What I am seeing instead is that a new persistent store is created for the iCloud account and no data.
My big question is what is the proper approach when it comes to the local fallback store? Where are my assumptions wrong?
This is one of the final pieces I need to ship my iOS 7 update. Any and all feedback would be greatly appreciated, and will be reflected in my github project so others can learn from my mistakes.
I have a duplicate of this question in the Apple Developer Forums. I will update this thread with any findings I get from there. I think this question is important and remains unresolved with the release of iOS 7. Fallback stores are a huge advancement in iCloud technology but the local storage part is still a little undefined.

I have worked around this for now since I cannot seem to get information as to how fallback stores are supposed to work in this scenario.
Basically what I do now is if the user is not logged in I create the document w/o iCloud sync options enabled.
Then at startup if iCloud is enabled I perform a scan for documents that need to be migrated and migrate them simply by opening them with the iCloud options enabled. Once opened I close the document as that is enough to get them migrated and scannable via a meta data scan.
Finally after the migration is done I kick off a new scan for documents.
It works but it is a bit of a hack.
Refer to the APManagedDocument commit:
421aaae

I finally got a reply tonight. I am not 100% certain he understood my question, but I am going to spend some time understanding his answer before I render a judgement.
Here is his response:
Thank you for your inquiry to Apple Worldwide Developer Technical
Support. I am responding to let you know that I have received your
request for technical assistance.
Core Data will not automatically move your UIManagedDocument to the
cloud for you. You need to create a new document in the ubiquity
container then migrate the persistent store from your local sandbox to
your ubiquity container. The migration is necessary to create all the
transaction logs so that other devices can create that document.
You could implement this class method to your UIManagedDocument
subclass:
(void)moveDocumentAtURL:(NSURL *)sourceDocumentURL toUbiquityContainer:(NSURL *)ubiquityContainerURL;
That method would essentially create a new document at
"ubiquityContainerURL", and you migrate the store from
"sourceDocumentURL" to "ubiquityContainerURL". You would use
"migratePersistentStore" to perform the migration.
Here's an example:
// The name of the file that contains the store identifier.
static NSString *DocumentMetadataFileName = #"DocumentMetadata.plist";
// The name of the file package subdirectory that contains the Core Data store when local.
static NSString *StoreDirectoryComponentLocal = #"StoreContent";
// The name of the file package subdirectory that contains the Core Data store when in the cloud. The Core Data store itself should not be synced directly, so it is placed in a .nosync directory.
static NSString *StoreDirectoryComponentCloud = #"StoreContent.nosync";
+ (NSDictionary *)optionsForStoreAtURL:(NSURL *)url {
NSURL *metadataDictionaryURL = [url URLByAppendingPathComponent:DocumentMetadataFileName];
NSDictionary __block *storeMetadata = nil;
/*
Perform a coordinated read of the store metadata file; the coordinated read ensures it is downloaded in the event that the document is cloud-based.
*/
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fileCoordinator coordinateReadingItemAtURL:metadataDictionaryURL options:0 error:NULL byAccessor:^(NSURL *newURL) {
storeMetadata = [[NSDictionary alloc] initWithContentsOfURL:newURL];
}];
NSString *persistentStoreUbiquitousContentName = nil;
if (storeMetadata != nil) {
persistentStoreUbiquitousContentName = [storeMetadata objectForKey:PersistentStoreUbiquitousContentNameKey];
if (persistentStoreUbiquitousContentName == nil) {
// Should not get here.
NSLog(#"ERROR in optionsForStoreAtURL:");
NSLog(#"persistentStoreUbiquitousContentName == nil");
abort();
}
}
else {
CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef uuidString = CFUUIDCreateString(NULL, uuid);
persistentStoreUbiquitousContentName = (__bridge_transfer NSString *)uuidString;
CFRelease(uuid);
}
// NSPersistentStoreUbiquitousContentURLKey should be the TransactionLogs directory.
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
persistentStoreUbiquitousContentName, NSPersistentStoreUbiquitousContentNameKey,
[[self URLForUbiquityTransactionLogs] URLByAppendingPathComponent:persistentStoreUbiquitousContentName] , NSPersistentStoreUbiquitousContentURLKey, nil];
return options;
}
+ (void)moveDocumentAtURL:(NSURL *)sourceDocumentURL toUbiquityContainer:(NSURL *)ubiquityContainerURL {
if (ubiquityContainerURL == nil) {
// iCloud isn't configured.
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
NSLocalizedString(#"iCloud does not appear to be configured.", #""), NSLocalizedFailureReasonErrorKey, nil];
NSError *error = [NSError errorWithDomain:#"Application" code:404 userInfo:dict];
NSLog(#"%#", [error localizedFailureReason]);
return;
}
// Move the document to the cloud using its existing filename
NSManagedObjectModel *model = [self managedObjectModel];
NSDictionary *ubiquitousOptions = [self optionsForStoreAtURL:sourceDocumentURL];
NSString *documentName = [[sourceDocumentURL lastPathComponent] stringByDeletingPathExtension];
documentName = [documentName stringByAppendingPathExtension:#"wwWhat"];
NSURL *destinationURL = [ubiquityContainerURL URLByAppendingPathComponent:documentName];
dispatch_queue_t q_default;
q_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(q_default, ^{
NSError __block *error = nil;
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
[coordinator coordinateWritingItemAtURL:destinationURL options:NSFileCoordinatorWritingForReplacing error:nil byAccessor:^(NSURL *destination) {
NSFileManager *fileManager = [[NSFileManager alloc] init];
[fileManager removeItemAtURL:destination error:nil];
NSURL *destinationStoreDirectoryURL = [destination URLByAppendingPathComponent:StoreDirectoryComponentCloud isDirectory:YES];
NSURL *destinationStoreURL = [destinationStoreDirectoryURL URLByAppendingPathComponent:StoreFileName isDirectory:NO];
NSURL *sourceStoreURL = [[sourceDocumentURL URLByAppendingPathComponent:StoreDirectoryComponentLocal isDirectory:YES] URLByAppendingPathComponent:StoreFileName isDirectory:NO];
NSURL *originalMetadataURL = [sourceDocumentURL URLByAppendingPathComponent:DocumentMetadataFileName isDirectory:NO];
NSURL *destinationMetadataURL = [destination URLByAppendingPathComponent:DocumentMetadataFileName isDirectory:NO];
[fileManager createDirectoryAtURL:destinationStoreDirectoryURL withIntermediateDirectories:YES attributes:nil error:nil];
[fileManager copyItemAtURL:originalMetadataURL toURL:destinationMetadataURL error:nil];
NSPersistentStoreCoordinator *pscForSave = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: model];
id store = [pscForSave addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sourceStoreURL options:nil error:nil];
id success = [pscForSave migratePersistentStore:store toURL:destinationStoreURL options:ubiquitousOptions withType:NSSQLiteStoreType error:&error];
if (success) {
[fileManager removeItemAtURL:sourceDocumentURL error:NULL];
}
else {
NSLog(#"Failed to migrate store: %#", error);
}
}];
});
}

Isn't his response consistent with what you are seeing, I.e. No data because you don't put the transaction logs out in the cloud so that other devices can recreate the document from the logs. By migrating I guess you have the log files automatically generated in the appropriate iCloud directory. But then the 207 video seems to indicate that any use of .sync folder is no longer required.
One good working example from them is all I ask for...
Any idea how one would access these files from OSX?
Btw your stuff looks pretty good, I hope to try using it in a couple of days. Am real keen to see how these files would be accessed from OSX. As I understand it NSPersistentDocument is not iCloud aware.
EDIT:
I just had a closer look at your APManagedDocumentManager and you don't seem to be including the iCloud path in the NSPersistentStoreUbiquitousContentURLKey value. Unless I missed something you are just using the subdirectory not the full iCloud path, also you're using a NSString rather than a URL (not sure whether this makes a difference).
EDIT: We should have a discussion over the phone perhaps? Anyway some more of my findings below:
I just installed Mavericks and after having gone over the video twice am testing the following:
Create new files using only the code below - no UIManagedDocument or anything. _storeURL is pointing to the local directory as suggested in the video. And I am not using any NSPersistentStoreUbiquitousContentURLKey because its not longer necessary. At the moment my filename has no UUID.
When I do this on any device then a CoreData directory is created outside the iCloud Documents directory. Inside the CoreData directory are subdirectories for each of the fileNames, and inside these are various zip files and things which are presumably baseline and log files. No sign of any DocumentMetaData.plist. So this all looks quite promising, except I can't figure out how one would "Discover" new files that appear. I am hoping that I just need to register for some notifications and am done... Going back to the video now because I can't recall the detail on what notifications are sent and how to react to them. At least the behaviour is consistent on both platforms. Strangely enough none of these documents show up in the Mac Apps File-Open dialog which lists all documents in the iCloud Documents directory, well not so strange I guess.
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
//FLOG(#" got_persistentStoreCoordinator is %#", _persistentStoreCoordinator);
FLOG(#" calling addPersistentStoreWithType for path %#", [_storeURL path]);
NSString *fileName = [[_storeURL URLByDeletingPathExtension] lastPathComponent];
FLOG(#" setting NSPersistent for path %#", [_storeURL path]);
#try {
store = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:_storeURL
options:#{NSPersistentStoreUbiquitousContentNameKey:fileName,
NSMigratePersistentStoresAutomaticallyOption:#YES,
NSInferMappingModelAutomaticallyOption:#YES,
NSSQLitePragmasOption:#{ #"journal_mode" : #"DELETE" }}
error:&error];
...

Related

Is it possible to have multiple core data "databases" on one iOS app?

I'm wanting to write a "management" game that utilizes Core data heavily. The game requires a pre-set, pre-defined dataset that cannot be changed by the user/system; it is used to seed the game with data and is meant to be read-only.
The best example I can give is a football management game, but it could be anything. In some football management sims they give you scenarios and pre-set datasets.
As the user proceeds through the game they can save/load their progress which is saved to the core data.
In addition to this, the user can receive updates to the pre-defined data or can purchase scenarios packs of data; which is saved to their device.
So, there could be multiple "core data databases" (yes, I know core data isn't strictly a database) or "buckets" which the app can dive into and use.
The schema of the data would not change.
So we have:
Pre-defined data (Default data) that is only used for seeding the game.
The user's current save game.
The user has downloaded a scenario from the Internet.
Problem: What happens when the user saves the game whilst on a "scenario".
Problem: How do I keep track of all the scenarios and all the user saved games in core data?
This sounds like multiple databases at a given time. Obviousily one should restrict how many save games a user can make.
An alternative solution to this is that the user's device exports a back-up copy of the data in JSON or XML and this serves as the "save data" and I could use this strategy for scenarios too. Obviousily some kind of encryption would be needed to prevent people simply changing stats in the game via the XML.
But I'm wondering from the outset what would be the best way to use Core data for iOS devices handle more than 1 core data "database"?
Thanks for your time
If the data models are the same, you can just setup your MOC so that it uses both persistent stores... one which is read-only and the other that is read/write.
Or, you could use separate MOC for each store.
So, how you want to use it is your only decision factor, since you can have almost any combination of MOC/PSC.
Look at the documentation here for more information.
Edit:
The link given with this question is dead, someone else suggested this link in another deleted answer.
NB: This is an old question, but the problems it describes are timeless, so I've written the answer as if the question were posted today.
Actually, none of this suggests the need for multiple databases. So we have:
1) Pre-defined data (Default data) that is only used for seeding the
game.
Write a method that loads the data into the persistent store (database). Set a flag in user default, defaultDataHasBeenLoaded or something like that, and check that in the appDelegata.
2) The user's current save game.
You need a Users table and a Games table with a one-to-many relationship. In the Games table you add an isCurrentGame attribute.
3) The user has downloaded a scenario from the Internet.
Now it's getting interesting. You will need an import function or class for that and you'll want to run that on a background thread. That way, your user can continue playing, or looking looking at their scores or whatever, while the new scenario is being imported. When the scenario has been imported, the user should get a notification and the opportunity to switch to the new scenario.
The most efficiënt way to do this is to use NSPeristentContainer which is available from iOS 10.0, macOS 10.12, tvOS 10.0 and watchOS 3.0. Give NSPeristentContainer the name of the data model and it will create or load a persistent store and set the persistentStoreCoördinator and the managedObjectContext.
// AppDelegate.h or class header file
#property (readonly, strong, nonatomic) NSPersistentContainer *persistentContainer;
#property (readonly, weak, nonatomic) NSManagedObjectContext *managedObjectContext;
// AppDelegate.m or other implementation file
#synthesize persistentContainer = _ persistentContainer;
#synthesize managedObjectContext = _ managedObjectContext;
- (NSPersistentContainer *)persistentContainer
{
#synchronized (self) {
if (_persistentContainer == nil) {
_persistentContainer = [[NSPersistentContainer alloc] initWithName:#"nameOfDataModel"];
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
if (error != nil) {
// Handle the error
} else {
_managedObjectContext = _persistentContainer.viewContext; // NB new name for moc is viewContext!
}
}];
}
}
return _persistentContainer;
}
To use the the container from the appDelegate in an NSViewController, you add the following to viewDidLoad:
self.representedObject = [(AppDelegate *)[[NSApplication sharedApplication] delegate] persistentContainer];
// Use representedObject in bindings, such as:
[_gameNameTextField bind:NSValueBinding toObject:self
withKeyPath:#"representedObject.game.name"
options:options];
To import the new scenario, use performBackgroundTask:, a block which will automatically create a new thread and a new managedObjectContext (here called moc_background). Use only moc_background for anything you do in the block -- if you call a method outside the block, pass it moc_background.
NSPersistentContainer *pc = (NSPersistentContainer *)self.representedObject;
pc.viewContext.automaticallyMergesChangesFromParent = YES; // this will ensure the main context will updated automatically
__block id newScenario;
[pc performBackgroundTask:^(NSManagedObjectContext * _Nonnull moc_background) {
NSEntityDescription *scenarioDesc = [NSEntityDescription entityForName:#"Scenario" inManagedObjectContext:moc_background];
NSManagedObject *scenario = [[NSManagedObject alloc] initWithEntity:scenarioDesc insertIntoManagedObjectContext:moc_background];
// configure scenario with the data from newScenario
NSError *error;
BOOL saved = [moc_background save:&error];
// send out a notification to let the rest of the app know whether the import was successfull
}];
Problem: What happens when the user saves the game whilst on a
"scenario".
That depends on who gets there first, the background thread that attempts to merge or the save operation. If you add a Scenario table with many-to-one relationship to the Game table, there should not be any problems.
Problem: How do I keep track of all the scenarios and all the user
saved games in core data?
Data modeling can be tricky. Keep it simple at first and add tables and relationships when you find a clear need for them. And then test, test, test.

iOS Simulator 6.0 crashes with custom sort descriptor

tAfter creating a Master-Detail Application project, I replaced the following line of code, in MasterViewController.m,
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
with
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"timeStamp"
ascending:YES
comparator:^NSComparisonResult (id obj1, id obj2)
{
return NSOrderedSame;
}];
This is the only change I've made to the Apple code template. When I run it on Simulator 6.0, the code crashes when fetching the data...
if (![self.fetchedResultsController performFetch:&error]) {
with objc_exception_throw. BTW, this code works on the Simulator 5.1. Any idea what's wrong? Thanks in advance.
Objective-C based sort descriptors cannot be used with a fetch request.
From the "Core Data Programming Guide":
... To summarize, though, if you execute a fetch directly, you should
typically not add Objective-C-based predicates or sort descriptors to
the fetch request. Instead you should apply these to the results of
the fetch.
Btw, do you really want a sort descriptor that makes all objects equal?

Bizarre Core Data behavior when fetching unsaved data

I have a Core Data importer that loops through data, and ignores duplicate records during the import process.
But I have discovered that my NSFetchRequest is not matching against recently stored records that have not been saved. And I am seeing seemingly identical queries delivering different and unexpected results.
For example, in my testing, I discovered that this query matches and returns results:
fetchTest.predicate = [NSPredicate predicateWithFormat:#"%K = 3882", #"intEmployee_id"];
But this seemingly identical one does not:
fetchTest.predicate = [NSPredicate predicateWithFormat:#"%K = %#", #"intEmployee_id", #"3882"];
But - they both match identically after the context is saved to the persistent store.
Apple's documentation says that fetches should work against pending changes by default, and indeed I have conformed that [fetchTest includesPendingChanges] = YES.
Any ideas what on earth is going on here? How is it possible that those two fetches return different results?
Maybe the employee id is not a string but a number? Then the predicate should be:
[NSPredicate predicateWithFormat:#"%K = %#", #"intEmployee_id",
[NSNumber numberWithInt:3882]];
This would imply that the erratic behavior comes from mixing up the types. It still works somehow, even if erratically, because in the SQLite docs it says that SQLite actually does not really distinguish by type when storing the data physically.
See Distinctive Features of SQLite from the SQLite web site, under the heading Manifest Typing.
These actually don't evaluate to the same value.
[NSPredicate predicateWithFormat:#"%K = 3882", #"intEmployee_id"]
evaluates to intEmployee_id = 3882 while
[NSPredicate predicateWithFormat:#"%K = %#", #"intEmployee_id", #"3882"]
evaluates to intEmployee_id = "3882"
Try using a number instead of a string for the id.

Persist data between views

I am developing an app in which the user enter a search word and receives results from a web service (I control the web service). The results are displayed on a uitable view and then the user can select a row and navigate to three - four level of inner details (I am using the uinavigation control).
I am wondering what is the best way to persist the data between the views.
I am currently using the application delegate to store an array of objects which I can access from everywhere in the app. It works fine but I read that it is not a good practice for the job (I am concerned about memory issues) . I tried using Core Data framework for the job but than I realized that I would have to read my web service results, and loop them one by one in order to insert them to database. I also will have to delete old data because the data I am saving is only actual for the current search. I read about p-lists and saving data to file system but could not find a real good solution for my job...
Any help will be appreciated!!! Thanks!!!
You can use JSON or PLISt for communication, although binary plist is - according to apple - much faster on the phone side.
Creating a dictionary from the binary Plist is relatively simple:
NSPropertyListFormat format;
NSDictionary *myDictionary = [NSPropertyListSerialization
propertyListFromData:data mutabilityOption:NSPropertyListMutableContainers
format:&format errorDescription:&errorString];
Creating it from a JSON just requires using one of the readily available JSON libraries. Once you have the dictionary, save it:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *file = [NSString stringWithFormat: #"%#%#.plist", [paths objectAtIndex:0], #"MyPlistBaseName"];
[plist writeToFile: file atomically:NO];
And re-load it later:
NSDictionary *plist = [[[NSDictionary alloc] initWithContentsOfFile: file] autorelease];
However, If you encapsulate the access to this data in a singleton, then you can worry about optimizing the implementation if speed / memory becomes an issue. Just treat that signleton as the 'owner' of the data, with methods like count, getGroup(0) (to return a block of 25), etc. Then you can hide all the implementation details inside the object.

Core Data and NSOperation

I'm currently working with an NSPersistentDocument subclass that uses NSOperation to import data in the background. As per the documentation, I'm observing the NSManagedObjectContextDidSaveNotification after saving in the background task and propagating the notification to the NSManagedObjectContext in the main thread using -mergeChangesFromContextDidSaveNotification:.
Everything works fine, but it presents a weird workflow for a user who's importing data into a new document. They need to save an empty document before doing the import (otherwise the -save: fails because the document hasn't configured a URL for the NSPersistentStoreCoordinator.) I don't see a way around this other than some kind of "new document setup" wizard that ensures -writeToURL:ofType:forSaveOperation:originalContentsURL:error: gets called before the import.
Also, it appears that an import task in the background precludes the use of an NSUndoManager on the main thread. (I'm assuming that it's unsafe to share the managed object context's undo manager across the threads.) From a user's point-of-view, there's no way to undo all the new objects created during the import.
I've read both the Core Data Programming Guide and Marcus Zarra's book, but I'm still new to this aspect of the framework. Hopefully, I've overlooked something: if not, I'll adapt my app to these restrictions (the benefits of Core Data far outweigh these user interface limitations.)
Thanks for your time!
--
Based on Peter Hosey's suggestion below, I added the following code to create a temporary persistent store prior to the import:
NSPersistentStoreCoordinator *persistentStoreCoordinator = [self.managedObjectContext persistentStoreCoordinator];
if ([[persistentStoreCoordinator persistentStores] count] == 0) {
// create an in-memory store to use temporarily
NSError *error;
NSPersistentStore *persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error];
if (! persistentStore) {
NSLog(#"error = %#", error); // TODO: better error handling
}
}
Then, after a file is selected in the save panel, the temporary persistent store is migrated to a SQLite store at the selected URL:
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation originalContentsURL:(NSURL *)absoluteOriginalContentsURL error:(NSError **)error
{
NSPersistentStoreCoordinator *persistentStoreCoordinator = [self.managedObjectContext persistentStoreCoordinator];
for (NSPersistentStore *persistentStore in [persistentStoreCoordinator persistentStores]) {
if (persistentStore.type == NSInMemoryStoreType) {
// migrate the in-memory store to a SQLite store
NSError *error;
NSPersistentStore *newPersistentStore = [persistentStoreCoordinator migratePersistentStore:persistentStore toURL:absoluteURL options:nil withType:NSSQLiteStoreType error:&error];
if (! newPersistentStore) {
NSLog(#"error = %#", error); // TODO: better error handling
}
}
}
return [super writeToURL:absoluteURL ofType:typeName forSaveOperation:saveOperation originalContentsURL:absoluteOriginalContentsURL error:error];
}
I'm nobody's Core Data expert, but from what I can tell from the docs, you'll want to start with an in-memory store until the user (in their own time) saves the document. Then, send the coordinator a migratePersistentStore:toURL:options:withType:error: message to change over from the in-memory store to the new truly-persistent store. See that document for some essential details (particularly regarding the fate of the store you migrate).
My first thought on the workflow/saving part would be, if a persistent store hasn't yet been created for the document, to create a temporary in-memory store, so that the imported data would be saved to that store instead (though the document/window would still be marked as dirty). Then, once the user saves the document for real, you would reconfigure the coordinator to remove the in-memory store and replace it with the on-disk store, so all further saves would go to disk.
I'm not 100% familiar with the Mac stuff, but I'm sure you could you use an in-memory persistent store before the user has saved, and then add the sql/plist store after that action.
Potentially even better could be to create a on-disk persistent store in a standard temporary directory, and move it across when the user clicks to save.
Have you tried setting up a temporary file URL when setting up the coordinator?
You should be able to undo the -mergeChangesFromContextDidSaveNotification: on the main thread. No need to register an undo manager for the MOC on the background thread.

Resources