0

I'm dealing to get my app work using a UIManagedDocument. I posted another question few days ago, but I haven't gotten it working yet. Let's see if I can do this better.
My app needs to load some data from a plist file and fill the database with it. Then, there are some parameters that will be changing now and again, and I also need to load and update them in the database.
The app should follow this sequence: Create/Open the database, load the data, update the variable data.
My problem comes when trying to follow that sequence correctly, mainly because I can't update the variable data before all objects are created in the database (and so the nil variables that have to be updated), and my workarounds only lead me to unexpected crashes and illogical behaviours.
Here's my code so far:

In the view code:

- (void)viewDidLoad{
    [super viewDidLoad];
    self.database = [DataHelper opendatabaseAndUseBlock:^{
        [self setupFetchedResultsController]; // I pass this method trough a Block because I want it
                                              // to be executed once the database is opened, not before.
    }];
}  

Then I use this helper class "DataHelper" which contains the following code:

@implementation DataHelper

// This method creates and opens the database, then calls the necessary methods
+ (UIManagedDocument *)openDatabaseAndUseBlock:(completion_block_t)completionBlock
{

    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    url = [url URLByAppendingPathComponent:@"Database"];
    UIManagedDocument *database = [[UIManagedDocument alloc] initWithFileURL:url];

    if (![[NSFileManager defaultManager] fileExistsAtPath:[database.fileURL path]]) {
        [database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
            [self loadDataIntodatabase:database withSuccess:success];
            completionBlock();
        }];

    } else if (database.documentState == UIDocumentStateClosed) {
        [database openWithCompletionHandler:^(BOOL success) {
            [self loadDataIntodatabase:database withSuccess:success];
            completionBlock();
        }];

    } else if (database.documentState == UIDocumentStateNormal) {
        [self loadDataIntodatabase:database withSuccess:YES];
        completionBlock();
    }

    return database;
}


// This method loads the "static" data into the database, by reading it from a plist file.
// Once the loading finishes, it should call the last method, for updating the variable data
+ (void)loadDataIntoDatabase:(UIManagedDocument *)database withSuccess:(BOOL)success
{
    if (success) {        
        NSString *path = [[NSBundle mainBundle] pathForResource:@"Data" ofType:@"plist"];
        NSArray *plistData = [NSArray arrayWithContentsOfFile:path];

        [database.managedObjectContext performBlock:^{
            for (NSDictionary *data in plistData) {
                [Object createObjectWithData:data inManagedObjectContext:database.managedObjectContext];
            }

            // Not sure what to do here!!
            //[database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
                 // [DataHelper updateVariableDataOfDatabase:database];
            //}];

            // Or...?
            //[database updateChangeCount:UIDocumentChangeDone];             
            // [DataHelper updateVariableDataOfDatabase:database];

        }];

    } else {
        NSLog(@"NO SUCCESS");
    }
}

// This last method updates some parameters of the existing data
// in the database, to the ones found in another plist file
+ (void)updateVariableDataOfDatabase:(UIManagedDocument *)database
{
    // This path is provisional, should be gotten from an online resource
    NSString *path = [[NSBundle mainBundle] pathForResource:@"VariableData" ofType:@"plist"];
    NSDictionary *variables = [NSDictionary dictionaryWithContentsOfFile:path];

    [database.managedObjectContext performBlock:^{
        // Update the objects changing the values of the variable parameters
        // to the ones found in "variables"

        // Now I should save the changes, or maybe not?
        //[database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil];
        // Or...
        //[database updateChangeCount:UIDocumentChangeDone];

    }];
}
@end

If I use the "saveToURL" method, although it seems to not be correct, it does the loading sequence correctly, because doesn't execute the last method until all the data has been loaded into the database. However, the app crashes randomly when trying to do many operations (with lots of savings inside, like deletion, updating or reloading).
On the other hand, by using "updateChangeCount", the app doesn't crash anymore, but the sequence doesn't work well: the variable data doesn't get updated, the app doesn't find an object that is there, on the database, and so on. If I wait 2 minutes or so between each action, then yes, it works...but that's not what I want.

I'd really appreciate some help here, this is consuming my time, and the deadline of this project is quite near :(
Thanks a lot!

David
  • 99
  • 2
  • 11

1 Answers1

0

Some time has passed and hopefully you've already resolved your issues here, but just in case as I've been fighting through a pile of UIManagedDocument related issues myself over the past couple months so I feel your pain. What you're doing here is fairly similar to what I needed to do in a recent project.

Unfortunately the documentation and various suggestions floating around online on how to save (or not save) a UIManagedDocument in general seems to be somewhat all over the place. I've found similar results using both the saveToURL and updateChangeCount approaches, however the updateChangeCount approach seems to less frequently result in save errors. But I don't think that is really impacting your issue.

As long as you only have one thread accessing your document and as long as you only have one document open for your data, you shouldn't actually need to save, it will automatically save within that context. Saving is needed to push changes out from that context to the other context (UIManagedDocument has two) and eventually out to the cloud and other devices. You have two layers of performBlock within your completionHandler which may be complicating things and leading to cases where more then one thread could be accessing your database at the same time possibly in an unpredictable order. When I do all of my preloading and setting it's all just sequentially within the completionHandler followed by the equivalent of your opendatabaseAndUseBlock and it never crashes.

joshOfAllTrades
  • 1,982
  • 14
  • 10