6

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;
}
Bart Jacobs
  • 9,022
  • 7
  • 47
  • 88
  • have you set any [`pragma` options](http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdPersistentStores.html#//apple_ref/doc/uid/TP40002875-SW12) on your store? – Dan Shelly May 27 '13 at 06:13
  • @DanShelly No. I have updated the question with the code snippet that you are referring to. – Bart Jacobs May 27 '13 at 06:23
  • 1
    When **exactly** do you save changes? – Tom Harrington May 27 '13 at 20:17
  • @TomHarrington The managed object context is saved immediately after the creation of a new managed object. In addition, the managed object context is also saved when the application goes to the background or terminates. – Bart Jacobs May 28 '13 at 04:55
  • Which application event method are you using to check for when it goes to background? – THE_DOM May 31 '13 at 14:56
  • 1
    Also what error handling do you do for when the app can't get access to the persistent store coordinator or when it cannot save? – THE_DOM May 31 '13 at 14:59
  • @THE_DOM The managed object context is saved on resign active and on application termination. In development, errors are remotely logged to TestFlight during development. In production, remote logging is disabled, which makes this all the harder to solve. I must admit that it sounds very plausible that the managed object context simply fails to save, which would explain the odd behavior. This means that I only need to find out why saving fails. – Bart Jacobs May 31 '13 at 15:43
  • @THE_DOM Can you submit an answer that I can accept? Your comment has led me to track down the problem so I think it is only fair to award the bounty to you. When the application attempted to save the managed object context, an error was thrown due to an uncommon validation error. – Bart Jacobs Jun 03 '13 at 04:37
  • @Bart answer written, glad you found it. – THE_DOM Jun 03 '13 at 14:59

3 Answers3

2

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];
}
THE_DOM
  • 4,266
  • 1
  • 18
  • 18
1

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.

Valerii Hiora
  • 1,542
  • 13
  • 8
1

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.

ecotax
  • 1,933
  • 17
  • 22
  • As I mentioned in the question, the managed object context is saved whenever a new entity is created so I am fairly certain that this is not the cause of the problem. Thanks for your answer. – Bart Jacobs Jun 03 '13 at 17:12