2010-02-14 48 views
75

我在苹果提供的示例代码中看到应该如何处理核心数据错误。即:iPhone核心数据“生产”错误处理

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

但从来没有一个如何应该实现它的任何实例。

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

由于提前, 马特

+5

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

回答

32

没有人会告诉你的生产代码,因为它在你的应用程序并发生错误取决于100%。

就我个人而言,我在此声明了一个断言,因为99.9%的时间会在开发过程中发生此错误,并且当您修复它时,它不会在生产中看到它。

声明之后,我会向用户显示一条警告,让他们知道发生了不可恢复的错误,并且应用程序将退出。您也可以在那里请求他们联系开发人员,以便您可以跟踪完成。

之后,我会在那里放弃abort(),因为它会“崩溃”应用程序,并生成一个堆栈跟踪,以便您稍后可以使用它来跟踪此问题。

+0

Marcus - 如果您正在与本地sqlite数据库或XML文件进行交谈,则断言无误,但如果您的持久存储是基于云的,则需要更强大的错误处理机制。 – dar512

+4

如果您的iOS Core Data持久性存储是基于云的,则会出现更大的问题。 –

+0

Hello @ MarcusS.Zarra,在这里你说你会使用abort()函数,但是苹果表示在运送应用程序时不会使用这个函数。所以你必须说 – Ranjit

31

这是我想出来处理和显示iPhone验证错误的一种通用方法。但马库斯是正确的:你可能想调整消息更加用户友好。但是这至少给你一个起点,看看什么领域没有验证,为什么。

- (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?:@""),([email protected]": ":@""),msg]; 
      } 
      UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Validation Error" 
                  message:messages 
                  delegate:nil 
                cancelButtonTitle:nil otherButtonTitles:@"OK", nil]; 
      [alert show]; 
      [alert release]; 
     } 
    } 
} 

享受。

+3

当然看不出这个代码有什么问题。看起来很稳固。我个人更喜欢用断言来处理核心数据错误。我还没有看到有人将其投入生产,所以我一直认为他们是发展错误,而不是潜在的生产错误。虽然这当然是另一个级别的保护:) –

+2

Marcus,关于断言:关于在验证方面保持代码DRY的意见是什么?在我看来,在模型(它所属的模型)中,只需定义一次验证标准是非常理想的:此字段不能为空,该字段必须至少有5个字符长,并且该字段必须与此正则表达式匹配。这应该是向用户显示适当的味精所需的全部信息。在保存MOC之前,它无法与我一起在代码中再次进行这些检查。你怎么看? –

+2

从来没有看到这个评论,因为它不是我的答案。即使你在模型中进行验证,仍然需要检查对象是否通过了验证并将其呈现给用户。取决于可能在现场级别的设计(此密码不好等)或保存点。设计师的选择。我不会让应用程序的那部分通用。 –

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用于记录在这里。

有关如何改善这一点的任何评论是更受欢迎!

BR 克里斯

+0

当我尝试使用回滚来实现这个时,我收到了奇怪的行为:http://stackoverflow.com/questions/34426719/why-is-nsmanagedobjectcontextobjectsdidchangenotification-called-twice-with-the-the – malhal

+0

我现在使用的是撤消 – malhal

2

我做@JohannesFahrenkrug的有用的答案可以是有用的斯威夫特版本:

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" 
}` 
3

我很惊讶这里没有一个人是真正处理错误的它意味着要处理的方式。如果你看文档,你会看到。

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

所以,如果我建立核心数据堆栈时发现错误,我换一个UIWindow的RootViewController的,并显示用户界面,清楚地告诉用户他们的设备可能已满,或者他们的安全设置过高此应用程序功能。我还给他们一个“再试一次”的按钮,这样他们可以在重新尝试核心数据堆栈之前尝试修复问题。

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

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

我也对网上的教程数量感到惊讶,因为这些原因也没有提到保存操作如何失败。因此,您需要确保应用程序中的任何保存事件可能会失败,因为设备只是这个分钟变得完整,您的应用程序节省了存储空间。

+0

这个问题是关于核心数据堆栈中的保存,它不是关于设置核心数据堆栈。但我同意它的标题可能是误导性的,也许应该修改它。 – valeCocoa

+0

我不同意@valeCocoa。这篇文章很清楚如何处理生产中的保存错误。再看看。 – 2017-09-14 08:35:02

+0

@roddanash这就是我说的...... WtH! :)再看看你的答案。 – valeCocoa