【问题标题】:iPhone Core Data "Production" Error HandlingiPhone Core Data“生产”错误处理
【发布时间】:2011-01-16 18:56:38
【问题描述】:

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

但从来没有任何例子说明你应该如何实施它。

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

提前致谢, 马特

【问题讨论】:

  • +1 这是一个很好的问题。

标签: iphone core-data error-handling


【解决方案1】:

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

就我个人而言,我在其中添加了一个断言语句,因为 99.9% 的时间这个错误将在开发中发生,当你在那里修复它时,不太可能在生产中看到它。

在断言之后,我会向用户发出警报,让他们知道发生了不可恢复的错误并且应用程序将退出。你也可以在里面放一个简介,让他们联系开发者,这样你就可以跟踪完成了。

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

【讨论】:

  • Marcus - 虽然在与本地 sqlite 数据库或 XML 文件通信时断言很好,但如果您的持久存储是基于云的,则需要更强大的错误处理机制。
  • 如果你的 iOS Core Data 持久化存储是基于云的,你就会遇到更大的问题。
  • 我在一些话题上不同意 Apple。这是教学环境(Apple)和战壕(我)之间的区别。从学术情况来看,是的,您应该删除中止。实际上,它们对于捕捉您从未想象过的情况很有用。 Apple 文档编写者喜欢假装每一种情况都有责任。其中 99.999% 是。你会为真正出乎意料的事情做些什么?我崩溃并生成了一个日志,这样我就可以找出发生了什么。这就是 abort 的用途。
  • @cschuff,这些都不会影响核心数据-save: 调用。所有这些情况都发生在您的代码达到这一点之前很久。
  • 这是一个预期的错误,可以在保存之前捕获和纠正。您可以询问 Core Data 数据是否有效并进行更正。另外,您可以在使用时对其进行测试,以确保所有有效字段都存在。这是一个开发人员级别的错误,可以在调用 -save: 之前很久就解决。
【解决方案2】:

这是我想出的一种在 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];
        }
    }
}

享受吧。

【讨论】:

  • 当然看不出这段代码有什么问题。看起来很结实。就我个人而言,我更喜欢用断言来处理 Core Data 错误。我还没有看到有人将其投入生产,所以我一直认为它们是开发错误而不是潜在的生产错误。虽然这肯定是另一层保护:)
  • Marcus,关于断言:您对在验证方面保持代码 DRY 有何看法?在我看来,在模型中(它所属的位置)只定义一次验证标准是非常可取的:该字段不能为空,该字段必须至少有 5 个字符长,并且该字段必须匹配这个正则表达式.这应该是向用户显示适当消息所需的所有信息。在保存 MOC 之前,在代码中再次进行这些检查并不适合我。你怎么看?
  • 从未见过此评论,因为它不在我的回答中。即使您将验证放入模型中,您仍然需要检查对象是否通过验证并将其呈现给用户。取决于可能在字段级别(此密码错误等)或保存点的设计。设计师的选择。我不会让应用程序的那部分通用。
  • @MarcusS.Zarra 我猜你从来没有得到它,因为我没有正确地@-提及你 :) 我想我们完全同意:我想要验证-信息 在模型中,但何时触发验证以及如何处理和呈现验证结果的决定不应该是通用的,应该在应用程序代码中的适当位置处理。
  • 代码看起来很棒。我唯一的问题是,在显示警报或记录分析之后,我应该回滚 Core Data 上下文还是中止应用程序?否则,我猜当您再次尝试保存时,未保存的更改将继续导致同样的问题。
【解决方案3】:

我很惊讶这里没有人真正按照应有的方式处理错误。如果您查看文档,您会看到。

此处出现错误的典型原因包括:* 设备已损坏 的空间。 * 持久存储不可访问,由于 设备锁定时的权限或数据保护。 * 这 store 无法迁移到当前模型版本。 * 这 父目录不存在、无法创建或不允许 写作。

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

例如,用户可以释放一些存储空间,返回我的应用程序并按下重试按钮。

断言?真的吗?房间里的开发人员太多了!

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

【讨论】:

  • 这个问题是关于保存在 Core Data 堆栈中的,而不是关于设置 Core Data Stack。但我同意它的标题可能会产生误导,也许应该修改它。
  • 我不同意@valeCocoa。该帖子显然是关于如何处理生产中的保存错误。再看看。
  • @roddanash 这就是我所说的……WtH! :) 再看看你的答案。
  • 你疯了兄弟
  • 您粘贴部分文档以了解在实例化持久存储时可能发生的错误,关于保存上下文时发生的错误的问题,而我是疯子?好的……
【解决方案4】:

我发现这个常见的保存功能是一个更好的解决方案:

- (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 进行日志记录。

欢迎任何关于如何改进的评论!

BR 克里斯

【讨论】:

【解决方案5】:

我制作了@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"
}`

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2010-09-22
  • 1970-01-01
  • 1970-01-01
  • 2013-10-27
  • 2011-03-06
  • 2019-11-01
  • 1970-01-01
相关资源
最近更新 更多