Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data reverts to previous state without apparent reason

Tags:

ios

core-data

A few customers of a Core Data based iOS application report that they occassionally lose data. The reports are very odd, which is the reason I'd like to ask for your take on this. The customers report that when they reopen the application after some time (minutes, hours, or next day), some of their data is lost as if the underlying database reverted to a previous state.

I have been working with Core Data for several years and have never run in an issue like this before. The application is fairly simple, which means I only use one managed object context and the changes are committed before the application goes to the background.

I realize that this is a long shot, but what could be some potential causes of this type of problem or what checks can I make to gather more information? Unfortunately, I cannot reproduce the issue myself, which would make all this a lot easier.

Update:

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) return _persistentStoreCoordinator;

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Prime.sqlite"];

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:@{ NSMigratePersistentStoresAutomaticallyOption : @YES, NSInferMappingModelAutomaticallyOption : @YES } error:&error]) {
        // Error Handling
    }

    return _persistentStoreCoordinator;
}
like image 585
Bart Jacobs Avatar asked May 27 '13 05:05

Bart Jacobs


Video Answer


3 Answers

You should check if save: method reports any error, for example like this:

NSError *error = nil;
if (![[self managedObjectContext] save:&error])
{
    NSLog(@"Error %@", action, [error localizedDescription]);    
    NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
    // if there is a detailed errors - we can dump all of them
    if(detailedErrors != nil && [detailedErrors count] > 0) {
        for(NSError* detailedError in detailedErrors) {
            NSLog(@"  DetailedError: %@", [detailedError localizedDescription]);
        }
    }
    else { // in other case lets dump all the info we have about the error
        NSLog(@"  %@", [error userInfo]);
    }
}

One of the most common fail reasons is validation error, for example, it may expect a field to be presented while user haven't entered it or it may expect the value to be less than XX characters and so on.

Basically that means if it takes too long to provide the client with new build with debug info you can ask them to send you example of data they use to enter.

like image 38
Valerii Hiora Avatar answered Oct 17 '22 00:10

Valerii Hiora


Check to see if you have put the save message in the appropriate appDelegate methods so that there is not a way to resign the application without saving. applicationWillResignActive and applicationWillTerminate should cover all of your needs.

Aside from that proper error handling and logging should gain you a lot of ground. I personally like to log these types of errors to a file that can be sent to me upon request. But that might be overkill for your particular application. This is written from memory so forgive any errors.

NSError *error = nil;
if (![[self managedObjectContext] save:&error])
{
    NSString *errorString = @"%@ ERROR : %@ %@", [[NSDate date] description], [error localizedDescription], [error userInfo]);

    NSString *documentsDirectory = [NSHomeDirectory() 
                                    stringByAppendingPathComponent:@"Documents"];

    NSString *filePath = [documentsDirectory 
                          stringByAppendingPathComponent:@"errorLog.txt"];

    // Write to the file
    [errorString writeToFile:filePath atomically:YES 
            encoding:NSUTF8StringEncoding error:&error];
}
like image 90
THE_DOM Avatar answered Oct 16 '22 23:10

THE_DOM


I can't be sure this is the reason, but it could be that when the app goes to the background, for some reason sometimes the maximum allowed time to handle this (backgroundTimeRemaining) is exceeded. From the Apple docs:

This property contains the amount of time the application has to run in the background before it may be forcibly killed by the system. While the application is running in the foreground, the value in this property remains suitably large. If the application starts one or more long-running tasks using the beginBackgroundTaskWithExpirationHandler: method and then transitions to the background, the value of this property is adjusted to reflect the amount of time the application has left to run.

If your app gets killed because saving the context takes too long, Core Data may decide to restore the previous state to at least get something consistent. If you can get log results from some users reporting this issue, you could try logging the property value after having tried to save the context, to check this.

like image 38
ecotax Avatar answered Oct 17 '22 01:10

ecotax