2010-02-14 40 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