Core Data Tips for iPhone Devs Part 2: Better Error Messages

Continuing our series of Core Data tips and tricks, we turn to error handling. All good developers know that error handling is an essential piece of quality software. Unfortunately for Cocoa developers, error handling can be a little cumbersome and verbose on our favorite platform. This is a small price to pay to be able to work with such outstanding libraries and APIs, but the constant code repetition can eat away at our keyboards. Frustratingly, the errors we work so hard to catch and handle are, sometimes, not useful at first glance. This is definitely the case when saving an NSManagedObjectContext.

The Problem

Early in development, the data model model changes often, and errors are inevitable. Forget to make a new attribute optional, and the code blows up while saving the context. Because I move pretty fast and change things often, this kind of stuff happens to me all the time. Core Data provides me this error message quite often:

Domain=NSCocoaErrorDomain Code=1560 UserInfo=0x14f5480 "Operation could not be completed. (Cocoa error 1560.)"

For the developer inexperienced with Core Data, this is a very disheartening message. It does not give us any indication of what went wrong or how we should attempt to fix the problem.

The Solution

It turns out that, if there is more than one error, Core Data puts an array of NSError objects in the userInfo dictionary with the key NSDetailedErrorsKey. This is not obvious, and it drove me crazy for a while until I turned to Stack Overflow where someone else had already asked my question. There, someone named Charles provided this solution with code to get at the "real" error. Overjoyed, I turned the code into a macro, and I use it exclusively to save a context during development. Here is the macro:

#define FT_SAVE_MOC(_ft_moc) \
do { \
  NSError* _ft_save_error; \
  if(![_ft_moc save:&_ft_save_error]) { \
    NSLog(@"Failed to save to data store: %@", [_ft_save_error localizedDescription]); \
    NSArray* _ft_detailedErrors = [[_ft_save_error userInfo] objectForKey:NSDetailedErrorsKey]; \
    if(_ft_detailedErrors != nil && [_ft_detailedErrors count] > 0) { \
      for(NSError* _ft_detailedError in _ft_detailedErrors) { \
        NSLog(@"DetailedError: %@", [_ft_detailedError userInfo]); \
      } \
    } \
    else { \
      NSLog(@"%@", [_ft_save_error userInfo]); \
    } \
  } \
} while(0);

Now, whenever I need to save the context, I can do it safely and comfortably like so:

FT_SAVE_MOC([self managedObjectContext])

This macro only prints the error messages, but all I want during development is error messages in the console. This code can easily be moved to a function or method to do more robust error handling in a production scenario, but that would fill an entire post on its own. 😉

3 Responses to “Core Data Tips for iPhone Devs Part 2: Better Error Messages”