views:

288

answers:

2

Hi there,

I've seen in the example code supplied by Apple references to how you should handle Core Data errors. I.e:

NSError *error = nil;
if (![context save:&error]) {
/*
 Replace this implementation with code to handle the error appropriately.

 abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
 */
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

But never any examples of how you should implement it.

Does anyone have (or can point me in the direction of) some actual "production" code that illustrates the above method.

Thanks in advance, Matt

+3  A: 

No one is going to show you production code because it depends 100% on your application and where the error occurs.

Personally, I put an assert statement in there because 99.9% of the time this error is going to occur in development and when you fix it there it is highly unlikely you will see it in production.

After the assert I would present an alert to the user, let them know an unrecoverable error occurred and that the application is going to exit. You can also put a blurb in there asking them to contact the developer so that you can hopefully track this done.

After that I would leave the abort() in there as it will "crash" the app and generate a stack trace that you can hopefully use later to track down the issue.

Marcus S. Zarra
Hi Marcus... Thanks for your response. Matt
Sway
+3  A: 

This is one generic method I came up with to handle and display validation errors on the iPhone. But Marcus is right: You'd probably want to tweak the messages to be more user friendly. But this at least gives you a starting point to see what field didn't validate and why.

- (void)displayValidationError:(NSError *)anError {
    if (anError && [[anError domain] isEqualToString:@"NSCocoaErrorDomain"]) {
        NSArray *errors = nil;

        // multiple errors?
        if ([anError code] == NSValidationMultipleErrorsError) {
            errors = [[anError userInfo] objectForKey:NSDetailedErrorsKey];
        } else {
            errors = [NSArray arrayWithObject:anError];
        }

        if (errors && [errors count] > 0) {
            NSString *messages = @"Reason(s):\n";

            for (NSError * error in errors) {
                NSString *entityName = [[[[error userInfo] objectForKey:@"NSValidationErrorObject"] entity] name];
                NSString *attributeName = [[error userInfo] objectForKey:@"NSValidationErrorKey"];
                NSString *msg;
                switch ([error code]) {
                    case NSManagedObjectValidationError:
                        msg = @"Generic validation error.";
                        break;
                    case NSValidationMissingMandatoryPropertyError:
                        msg = [NSString stringWithFormat:@"The attribute '%@' mustn't be empty.", attributeName];
                        break;
                    case NSValidationRelationshipLacksMinimumCountError:  
                        msg = [NSString stringWithFormat:@"The relationship '%@' doesn't have enough entries.", attributeName];
                        break;
                    case NSValidationRelationshipExceedsMaximumCountError:
                        msg = [NSString stringWithFormat:@"The relationship '%@' has too many entries.", attributeName];
                        break;
                    case NSValidationRelationshipDeniedDeleteError:
                        msg = [NSString stringWithFormat:@"To delete, the relationship '%@' must be empty.", attributeName];
                        break;
                    case NSValidationNumberTooLargeError:                 
                        msg = [NSString stringWithFormat:@"The number of the attribute '%@' is too large.", attributeName];
                        break;
                    case NSValidationNumberTooSmallError:                 
                        msg = [NSString stringWithFormat:@"The number of the attribute '%@' is too small.", attributeName];
                        break;
                    case NSValidationDateTooLateError:                    
                        msg = [NSString stringWithFormat:@"The date of the attribute '%@' is too late.", attributeName];
                        break;
                    case NSValidationDateTooSoonError:                    
                        msg = [NSString stringWithFormat:@"The date of the attribute '%@' is too soon.", attributeName];
                        break;
                    case NSValidationInvalidDateError:                    
                        msg = [NSString stringWithFormat:@"The date of the attribute '%@' is invalid.", attributeName];
                        break;
                    case NSValidationStringTooLongError:      
                        msg = [NSString stringWithFormat:@"The text of the attribute '%@' is too long.", attributeName];
                        break;
                    case NSValidationStringTooShortError:                 
                        msg = [NSString stringWithFormat:@"The text of the attribute '%@' is too short.", attributeName];
                        break;
                    case NSValidationStringPatternMatchingError:          
                        msg = [NSString stringWithFormat:@"The text of the attribute '%@' doesn't match the required pattern.", attributeName];
                        break;
                    default:
                        msg = [NSString stringWithFormat:@"Unknown error (code %i).", [error code]];
                        break;
                }

                messages = [messages stringByAppendingFormat:@"%@%@%@\n", (entityName?:@""),(entityName?@": ":@""),msg];
            }
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Validation Error" 
                                                            message:messages
                                                           delegate:nil 
                                                  cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
            [alert show];
            [alert release];
        }
    }
}

Enjoy.

Johannes Fahrenkrug
Certainly cannot see anything wrong with this code. Looks solid. Personally I prefer to handle Core Data errors with an assertion. I have yet to see one make it to production so I have always considered them to be development errors rather than potential production errors. Although this is certainly another level of protection :)
Marcus S. Zarra
Thanks, Marcus, I appreciate the feedback.
Johannes Fahrenkrug
Marcus, about assertions: What is your opinion on keeping code DRY in terms of validations? In my opinion it is very desirable to define your validation criteria only once, in the model (where it belongs): This field can't be empty, that field has to be at least 5 chars long and that field has to match this regex. That _should_ be all the information needed to display an appropriate msg to the user. It somehow doesn't sit well with me to do those checks again in code before saving the MOC. What do you think?
Johannes Fahrenkrug