iPhone核心数据“生产”错误处理


84

我已经在Apple参考提供的示例代码中看到了有关如何处理Core Data错误的信息。即:

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();
}

但从来没有的你怎么什么例子应该实现它。

是否有人有(或可以向我指出)一些说明上述方法的实际“生产”代码。

预先感谢,马特


7
+1这是一个很好的问题。
戴夫·德隆

Answers:


32

没有人会向您显示生产代码,因为它取决于您的应用程序和错误发生位置的100%。

就我个人而言,我在其中放置一个assert语句,因为99.9%的时间该错误将在开发中发生,并且在此位置进行修复时,不可能在生产中看到它。

在断言之后,我将向用户显示警报,让他们知道发生了不可恢复的错误,并且该应用程序将要退出。您还可以在其中放置一个简介,要求他们与开发人员联系,以便您希望可以跟踪完成情况。

之后,我将其中的abort()留在那里,因为它将“崩溃”该应用程序并生成一个堆栈跟踪,您希望以后可以使用它来跟踪问题。


Marcus-虽然断言在与本地sqlite数据库或XML文件进行通讯时很好,但如果您的持久性存储基于云,则需要更强大的错误处理机制。
dar512 2013年

4
如果您的iOS Core Data持久性存储基于云,那么您将遇到更大的问题。
Marcus S. Zarra

3
我在许多主题上与苹果不同意。这是教学状况(苹果)和战((我)之间的差异。从学术情况来看,是的,您应该删除堕胎。实际上,它们对于捕获您从未想象过的情况很有用。苹果文档作家喜欢假装每一种情况都是负责任的。其中99.999%是。您为真正意想不到的事情做什么?我崩溃并生成了日志,因此我可以找出发生了什么。那就是中止的目的。
Marcus S. Zarra 2014年

1
@cschuff,这些都不会影响核心数据-save:调用。所有这些条件都发生在代码到达这一点之前很长时间。
Marcus S. Zarra 2014年

3
这是可以在保存之前捕获并纠正的预期错误。您可以询问核心数据,该数据是否有效并进行更正。另外,您可以在使用时进行测试以确保所有有效字段都存在。这是开发人员级别的错误,可以在-save:调用之前很长时间进行处理。
Marcus S. Zarra 2014年

32

这是我想出的一种通用方法,用于处理和显示iPhone上的验证错误。但是Marcus是正确的:您可能希望调整消息以使其更加用户友好。但这至少为您提供了一个起点,以查看哪些字段未验证以及原因。

- (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];
        }
    }
}

请享用。


3
当然看不到此代码有什么问题。看起来很坚固。我个人更喜欢使用断言来处理Core Data错误。我还没有看到一个可以投入生产的产品,所以我一直认为它们是开发错误,而不是潜在的生产错误。尽管这当然是另一种保护级别:)
Marcus S. Zarra

2
马库斯(Marcus)关于断言:就验证而言,您对保持代码DRY有何看法?我认为非常希望在模型(它所属的模型)中只定义一次验证条件:该字段不能为空,该字段必须至少为5个字符,并且该字段必须匹配此正则表达式。那应该是向用户显示适当的味精所需的所有信息。在保存MOC之前,用某种方式再次在代码中进行这些检查不太适合我。你怎么看?
Johannes Fahrenkrug'9

2
从来没有看到过此评论,因为它不在我的回答中。即使将验证放入模型中,您仍然需要检查对象是否通过验证并将其呈现给用户。根据可能在现场级别(此密码错误等)或保存点的设计而定。设计师的选择。我不会将该应用程序的一部分设为通用。
Marcus S. Zarra

1
@ MarcusS.Zarra我想您没有得到它,因为我没有正确@提及您:)我想我们完全同意:我希望验证信息包含在模型中,但是决定何时触发验证和如何处理和显示验证结果不应是通用的,应在应用程序代码的适当位置进行处理。
Johannes Fahrenkrug

代码看起来很棒。我唯一的问题是,在显示警报或记录分析后,我应该回滚Core Data上下文还是中止应用程序?否则,我想当您再次尝试保存时,未保存的更改将继续引起相同的问题。
杰克

6

令我惊讶的是,这里没有人实际上按照应处理的方式处理错误。如果您查看文档,将会看到。

出现错误的典型原因包括:*设备空间不足。*由于设备锁定时的权限或数据保护,无法访问持久性存储。*无法将商店迁移到当前模型版本。*父目录不存在,无法创建或不允许写入。

因此,如果在设置核心数据堆栈时发现错误,则会交换UIWindow的rootViewController并显示UI,该UI清楚地告诉用户其设备可能已满,或者其安全设置太高而无法运行此App。我还为他们提供了“重试”按钮,因此他们可以尝试在重新尝试核心数据堆栈之前解决此问题。

例如,用户可以释放一些存储空间,返回到我的应用程序,然后按“重试”按钮。

断言?真?会议室中的开发人员太多!

我也对在线教程的数量感到惊讶,这些教程没有提到保存操作也可能由于这些原因而失败。因此,您将需要确保应用程序中的任何保存事件都可能失败,因为设备的“仅此分钟”已满,并且您的应用程序保存了保存。


这个问题与保存在核心数据栈中有关,而不是与设置核心数据栈有关。但是我同意它的标题可能会误导人们,也许应该对其进行修改。
valeCocoa

我不同意@valeCocoa。该帖子显然是关于如何处理生产中的保存错误。再看一遍。

我说的是@roddanash ... :)再看看您的答案。
valeCocoa

您是疯子吗

您将文档的一部分粘贴到实例化持久性存储时可能发生的错误中,有关存储上下文时发生的错误的问题,我是疯了吗?好的…
valeCocoa

5

我发现此通用保存功能是一个更好的解决方案:

- (BOOL)saveContext {
    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        DDLogError(@"[%@::%@] Whoops, couldn't save managed object context due to errors. Rolling back. Error: %@\n\n", NSStringFromClass([self class]), NSStringFromSelector(_cmd), error);
        [self.managedObjectContext rollback];
        return NO;
    }
    return YES;
}

每当保存失败时,这都会回滚您的NSManagedObjectContext,这意味着它将重置自上次保存以来在上下文中执行的所有更改。因此,您必须格外小心,以确保始终尽早并定期使用上述保存功能永久保存更改,因为否则可能容易丢失数据。

对于插入数据,这可能是一个宽松的变体,允许进行其他更改:

- (BOOL)saveContext {
    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        DDLogError(@"[%@::%@] Whoops, couldn't save. Removing erroneous object from context. Error: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), object.objectId, error);
        [self.managedObjectContext deleteObject:object];
        return NO;
    }
    return YES;
}

注意:我正在使用CocoaLumberjack在这里记录。

欢迎您对如何改善此问题发表任何评论!

克里斯·克里斯


我得到奇怪的行为,当我尝试使用回滚来实现这一目标:stackoverflow.com/questions/34426719/...
malhal

我现在改用undo
malhal

2

我对@JohannesFahrenkrug的有用答案做了一个Swift版本,它可能是有用的:

public func displayValidationError(anError:NSError?) -> String {
    if anError != nil && anError!.domain.compare("NSCocoaErrorDomain") == .OrderedSame {
        var messages:String = "Reason(s):\n"
        var errors = [AnyObject]()
        if (anError!.code == NSValidationMultipleErrorsError) {
            errors = anError!.userInfo[NSDetailedErrorsKey] as! [AnyObject]
        } else {
            errors = [AnyObject]()
            errors.append(anError!)
        }
        if (errors.count > 0) {
            for error in errors {
                if (error as? NSError)!.userInfo.keys.contains("conflictList") {
                    messages =  messages.stringByAppendingString("Generic merge conflict. see details : \(error)")
                }
                else
                {
                    let entityName = "\(((error as? NSError)!.userInfo["NSValidationErrorObject"] as! NSManagedObject).entity.name)"
                    let attributeName = "\((error as? NSError)!.userInfo["NSValidationErrorKey"])"
                    var msg = ""
                    switch (error.code) {
                    case NSManagedObjectValidationError:
                        msg = "Generic validation error.";
                        break;
                    case NSValidationMissingMandatoryPropertyError:
                        msg = String(format:"The attribute '%@' mustn't be empty.", attributeName)
                        break;
                    case NSValidationRelationshipLacksMinimumCountError:
                        msg = String(format:"The relationship '%@' doesn't have enough entries.", attributeName)
                        break;
                    case NSValidationRelationshipExceedsMaximumCountError:
                        msg = String(format:"The relationship '%@' has too many entries.", attributeName)
                        break;
                    case NSValidationRelationshipDeniedDeleteError:
                        msg = String(format:"To delete, the relationship '%@' must be empty.", attributeName)
                        break;
                    case NSValidationNumberTooLargeError:
                        msg = String(format:"The number of the attribute '%@' is too large.", attributeName)
                        break;
                    case NSValidationNumberTooSmallError:
                        msg = String(format:"The number of the attribute '%@' is too small.", attributeName)
                        break;
                    case NSValidationDateTooLateError:
                        msg = String(format:"The date of the attribute '%@' is too late.", attributeName)
                        break;
                    case NSValidationDateTooSoonError:
                        msg = String(format:"The date of the attribute '%@' is too soon.", attributeName)
                        break;
                    case NSValidationInvalidDateError:
                        msg = String(format:"The date of the attribute '%@' is invalid.", attributeName)
                        break;
                    case NSValidationStringTooLongError:
                        msg = String(format:"The text of the attribute '%@' is too long.", attributeName)
                        break;
                    case NSValidationStringTooShortError:
                        msg = String(format:"The text of the attribute '%@' is too short.", attributeName)
                        break;
                    case NSValidationStringPatternMatchingError:
                        msg = String(format:"The text of the attribute '%@' doesn't match the required pattern.", attributeName)
                        break;
                    default:
                        msg = String(format:"Unknown error (code %i).", error.code) as String
                        break;
                    }

                    messages = messages.stringByAppendingString("\(entityName).\(attributeName):\(msg)\n")
                }
            }
        }
        return messages
    }
    return "no error"
}`
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.