Search code examples
iphoneioscore-datansmanagedobjectcontext

How to ensure NSManagedObjectContext when opened asynchronously through UIManagedDocument


I have an application with different controllers that all operate on the same NSManagedObjectContext.

My approach was to initialize the NSManagedObjectContext in my AppDelegate and inject it into all the controllers.

I am initializing my NSManagedObjectContext by opening a UIManagedDocument like this:

UIManagedDocument* databaseDoc = [[UIManagedDocument alloc] initWithFileURL:url];

if (![[NSFileManager defaultManager] fileExistsAtPath:[databaseDoc.fileURL path]]) {
    [databaseDoc saveToURL:databaseDoc.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
        myController.managedObjectContext = databaseDoc.managedObjectContext;
    }];
} else if (databaseDoc.documentState == UIDocumentStateClosed) {
    [databaseDoc openWithCompletionHandler:^(BOOL success) {
        myController.managedObjectContext = databaseDoc.managedObjectContext;
    }];
} else if (databaseDoc.documentState == UIDocumentStateNormal){
    myController.managedObjectContext = databaseDoc.managedObjectContext;
}

Now my problem is, that opening the UIManagedDocument happens asynchronously and the NSManagedObjectContext is only available in the completion block.

How do I ensure that the controllers always have a valid NSManagedObjectContext to work with? Of course the problems happen at startup i.e. when a controller wants to use the NSManagedObjectContext in his "viewDidLoad" method, and the completion block in the AppDelegate has not yet run ...

One approach would probably be to "wait" in the AppDelegate until the UIDocument has opened, but as far as I gather this is not recommended ...

I would like to avoid to "pollute" my controllers with code that deals with the asynchronous nature of opening a NSManagedObjectContext... but maybe this is a naive wish?


Solution

  • In your appDelegate:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        MyWaitViewController* waitController = [[MyWaitViewController new] autorelease];
        self.window.rootViewController =  waitController;
    
    // then somewheres else, when you get your context
      [databaseDoc saveToURL:databaseDoc.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
            myContextController.managedObjectContext = databaseDoc.managedObjectContext;
            self.window.rootViewController    = myContextController;
            // note that at this point when the viewDidLoad method will get called
            // it will have his managedObjectContext and his view already available.
            // you can change your rootController, or push another viewController into the
     // stack. Depending on what u want from the GUI side
    
        }];
        return YES;
    }
    

    Note that you dispose the GUI logic into the MyWaitViewController + AppDelegate side. But you keep your "myContextController" away from that logic control, since he get called / created only when a context exist.