Question

I have a singleton class (DTTSingleton) with the following methods:

+ (UIManagedDocument *)managedDocument
{    
    static UIManagedDocument *managedDocument = nil;
    static dispatch_once_t mngddoc;

    dispatch_once(&mngddoc, ^
    {
        if(!managedDocument)
        {
            NSURL *url = [[DTTHelper applicationDocumentsDirectory] URLByAppendingPathComponent:kDTTDatabaseName];
            managedDocument = [[DTTManagedDocument alloc] initWithFileURL:url];            
        }
    });

    return managedDocument;
}

+ (void)useDefaultDocumentWithBlock:(completion_block_t)completionBlock
{
    if (![[NSFileManager defaultManager] fileExistsAtPath:[DTTSingleton.managedDocument.fileURL path]])
    {
        [DTTSingleton.managedDocument saveToURL:DTTSingleton.managedDocument.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success)
         {
             if (success)
             {
                 completionBlock(DTTSingleton.managedDocument.managedObjectContext);                 
             }
             else
             {
                 NSLog(@"Failed to save!");
             }
         }];
    }
    else if (DTTSingleton.managedDocument.documentState == UIDocumentStateClosed)
    {
        [DTTSingleton.managedDocument openWithCompletionHandler:^(BOOL success)
         {
             if (success)
             {
                 completionBlock(DTTSingleton.managedDocument.managedObjectContext);
             }
             else
             {
                 NSLog(@"Failed to open!");
             }
         }];
    }
    else if (DTTSingleton.managedDocument.documentState == UIDocumentStateNormal)
    {
        completionBlock(DTTSingleton.managedDocument.managedObjectContext);
    }
}

And in my UITableViewController I have the following code in the viewDidLoad method:

   [DTTSingleton useDefaultDocumentWithBlock:^(NSManagedObjectContext *moc)
    {
        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"SomeEntity"];
        self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc cacheName:nil];
    }];
    [DTTSingleton useDefaultDocumentWithBlock:^(NSManagedObjectContext *moc)
     {
         NSLog(@"When this is called it errors because DTTSingleton is already trying to open it!");
     }];

When executed I get the error:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to open or a revert document that already has an open or revert operation in flight

I understand why I'm getting this error, it's because I'm trying to open the document when another opening process is already running. So my question are...

1) How do I ensure openWithCompletionHandler is only called once?

2) How do I ensure the second block is executed once the document has opened?

Thanks for any help!

Was it helpful?

Solution 2

Just thought I'd post back as say the issues I was having have been solved by disabling buttons that access Core Data until the document is ready, this way you'll never try to open the document at the same time as another process. And as for Core Data access in a life cycle handler like viewDidLoad, I implemented a system where by if the document is opening (state kept manually with a variable) it delays the call by looping until the document is open, in essence queuing the calls. Don't forget to use performSelector:withObject:afterDelay: in the call within to loop otherwise you'll get an application crash.

Thanks for your suggestions John.

OTHER TIPS

I'm not sure if you've seen this yet, but a good resource would probably be here: http://adevelopingstory.com/blog/2012/03/core-data-with-a-single-shared-uimanageddocument.html

In the event that you're just trying to create your own (rather than using the above link - or similar) and are looking for some input, I can point out a few things I see (though I do not claim by any means to be an expert)...

Anyways, I believe your issue stems here:

// ....
} else if(DTTSingleton.managedDocument.documentState == UIDocumentStateClosed) {

    [DTTSingleton.managedDocument openWithCompletionHandler:^(BOOL success) {
         if(success) {
             completionBlock(DTTSingleton.managedDocument.managedObjectContext);
         } else {
             NSLog(@"Failed to open!");
         }
     }];

}

The method openWithCompletionHandler attempts to open a connection to the document asynchronously. The issue with this is that, on your first call to open the document in your UITableView, the code you're using notices the document is closed - so it attempts to open it. This is all fine and dandy, but the code you're using here then re-issues yet another attempt to create an instance of the singleton. More than likely, this is happening so fast (and close together) that it, yet again, attempts to open the document asynchronously.

To test this, try putting a breakpoint after the UIDocumentStateClosed check for the line:

[DTTSingleton.managedDocument openWithCompletionHandler:^(BOOL success)

I believe you'll see this being executed numerous times...

I'm not skilled enough to explain how to solve this, but I would seriously recommend using the approach shown in the link above where he applies a block to assign/track the existence of an open document

Hopefully that helps?

Edit: * Added the suggestion for the breakpoint.

Edit: Here is another stackoverflow ticket with a similar issue (and suggestion/conclusion) - so I may not be too far off here after all =)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top