Monday, November 14, 2011

What is in my Core Data managed store?

Suppose you have an iOS application. Suppose it uses the Core Data API to persist data. And let us further suppose that you are having a few problems; it seems like the data in your store isn't exactly as you expect.

It might prove useful to be able to get some basic information about what the heck is happening with your data at runtime. For example, let us suppose you have an API in your code that lets you get a reference to the NSManagedObjectModel. We can then print out what entities are in your database and how many of them. Create a simple ViewController with a multi-line text view on it and setup an outlet named 'output'. In the viewDidAppear method gather up some basic diagnostic information and spit it out to the output text view:
//NOTE: sample assumes ARC, hence no calls to release
//in the .m file for your diagnostic output

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
    NSString *diagnostics = @"";
    
    @try {
        diagnostics = [NSString stringWithFormat:@"%@%@", diagnostics, @"Data Diagnostics\n"];
        NSManagedObjectModel *mom = //CALL WHATEVER YOUR APP USES TO GET NSManagedObjectModel
        if (mom) {
            for (NSEntityDescription *entity in mom.entities) {
                NSUInteger countOf = [self countOfEntity:entity];
                diagnostics = [NSString stringWithFormat:@"%@  entity:%@ (%d records)\n", diagnostics, entity.name, countOf];
            }
        } else {
            diagnostics = [NSString stringWithFormat:@"%@%@", diagnostics, @"  objectModel is nil :(\n"];
        }
    }
    @catch (NSException *exception) {
        diagnostics = [NSString stringWithFormat:@"%@%@", diagnostics, @"  ERROR getting data diagnostics\n"];
    }
    
    output.text = diagnostics;
}

//note that this could return "NSNotFound if an error occurs"; this typically shouldn't be the case since we got the entity description directly from our object model
- (NSUInteger) countOfEntity :(NSEntityDescription*)entity
{
    NSFetchRequest *fr = [[NSFetchRequest alloc] init];
    fr.entity = entity;
    [fr setIncludesSubentities:NO];
    
    NSError *err;
 NSManagedObjectContext *moc = //CALL WHATEVER YOUR APP USES TO GET NSManagedObjectContext
    NSUInteger count = [moc countForFetchRequest:fr error:&err];
    return count;
}
If you print this to a text view your output should end up something like this:
For some types of app this is surprisingly useful.

The main things I personally find this useful for are:

  1. Identifying glaring errors, such as "your model is completely missing an entity type; something is very wrong"
  2. Identifying errors where data isn't added or removed as expected, such as "when you do X in the UI there should be +1 of those (or -2 of those or whatever) if I quickly peek at diagnostics"
    1. Hmm ... perhaps it would be useful if the diagnostic view actually told you the delta for each type since you last looked?
There are many ways to wire this in; it doesn't have to be via it's own view. Another approach I often find useful to to have a debug/logged mode enabled only in development where this type of information prints to the console when key events occur in the app.