Core Data and NSOperation
Asked Answered
P

4

5

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];
}
Platoon answered 7/2, 2011 at 19:36 Comment(1)
As your on a mac, couldn't you use an in memory store, and then switch the context to use a file based store when your ready? The above would also solve the problem with the undo, as you could just dispense with the whole context stack if need be. Seems while i was writing this everybody was writing the same :)Orangeade
R
5

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

Ransdell answered 7/2, 2011 at 19:46 Comment(1)
Thanks Peter! The migration was the piece of the puzzle that was missing -- I've updated my post above.Platoon
H
2

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.

Haletky answered 7/2, 2011 at 19:43 Comment(0)
M
2

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.

Mure answered 7/2, 2011 at 19:45 Comment(0)
O
0

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.

Overset answered 7/2, 2011 at 19:43 Comment(1)
Good solution, however Like Daniel, Peter & Brian said, simply creating a in-memory store is just neater, and not to mention faster. However this would have to be an alternative if we talking about a very large dataset.Orangeade

© 2022 - 2024 — McMap. All rights reserved.