2010-02-14 1 views
75

Ich habe in dem von Apple bereitgestellten Beispielcode gesehen, wie Sie mit Core Data-Fehlern umgehen sollten. Das heißt:iPhone Core Data "Produktion" Fehlerbehandlung

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

aber nie irgendwelche Beispiele, wie Sie sollte es umzusetzen.

Hat jemand (oder kann mich in Richtung zeigen) einige tatsächliche "Produktion" Code, der die oben genannte Methode veranschaulicht.

Vielen Dank im Voraus, Matt

+5

+1 das ist eine ausgezeichnete Frage. –

Antwort

32

Niemand wird Ihnen Produktion Code zu erhalten, weil es 100% auf Ihre Anwendung abhängt und wo der Fehler auftritt.

Ich persönlich legte eine assert-Anweisung in dort, weil 99,9% der Zeit, dieser Fehler in der Entwicklung auftreten wird, und wenn Sie es dort zu beheben ist es sehr unwahrscheinlich, dass Sie es in der Produktion sehen.

Nach der Assert würde ich eine Warnung an den Benutzer präsentieren, lassen Sie sie wissen, dass ein nicht behebbarer Fehler aufgetreten ist und dass die Anwendung beendet wird. Sie können auch einen Klappentext einfügen, in dem Sie gebeten werden, den Entwickler zu kontaktieren, damit Sie dies hoffentlich verfolgen können.

Danach würde ich den abort() da drin lassen, da es die App "abstürzen" und einen Stack-Trace erzeugen wird, den Sie hoffentlich später verwenden können, um das Problem aufzuspüren.

+0

Marcus - Während behauptet wird, dass es gut ist, wenn Sie mit einer lokalen SQLite-Datenbank oder XML-Datei sprechen, benötigen Sie einen robusteren Fehlerbehandlungsmechanismus, wenn Ihr persistenter Speicher cloudbasiert ist. – dar512

+4

Wenn Ihr persistenter Store für iOS-Core-Daten cloudbasiert ist, haben Sie größere Probleme. –

+0

Hallo @ MarcusS.Zarra, hier sagen Sie, dass Sie abort() -Funktion verwenden werden, aber Apfel sagt nicht oben diese Funktion in der Versandanwendung verwenden. Also, was Sie zu sagen haben – Ranjit

31

Dies ist eine generische Methode, die ich entwickelt habe, um Validierungsfehler auf dem iPhone zu behandeln und anzuzeigen. Aber Marcus hat Recht: Sie möchten wahrscheinlich die Nachrichten optimieren, um benutzerfreundlicher zu sein. Aber das gibt Ihnen zumindest einen Ausgangspunkt, um zu sehen, welches Feld nicht validiert wurde und warum.

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

Genießen.

+3

Sicher kann nichts mit diesem Code falsch sehen. Sieht solide aus. Persönlich bevorzuge ich Core Data Fehler mit einer Assertion zu behandeln. Ich habe noch nicht gesehen, wie man es in die Produktion schafft, daher habe ich sie immer als Entwicklungsfehler und nicht als mögliche Produktionsfehler betrachtet. Obwohl dies sicherlich eine andere Ebene des Schutzes ist :) –

+2

Marcus, über Behauptungen: Was ist Ihre Meinung darüber, Code DRY in Bezug auf Validierungen zu halten? Meiner Meinung nach ist es sehr wünschenswert, Ihre Validierungskriterien nur einmal im Modell (wo es hingehört) zu definieren: Dieses Feld darf nicht leer sein, dieses Feld muss mindestens 5 Zeichen lang sein und dieses Feld muss mit dieser Regex übereinstimmen . Das _sollte _ alle Informationen sein, die benötigt werden, um dem Benutzer eine geeignete Nachricht anzuzeigen. Es passt irgendwie nicht gut zu mir, diese Checks nochmal im Code zu machen, bevor ich das MOC speichere. Was denken Sie? –

+2

Niemals diesen Kommentar gesehen, da es nicht auf meiner Antwort war. Auch wenn Sie die Validierung in das Modell einfügen, müssen Sie noch überprüfen, ob das Objekt die Validierung bestanden hat und diese dem Benutzer präsentieren. Je nach Design, das auf Feldebene (dieses Passwort ist schlecht, etc.) oder am Speicherpunkt liegen könnte. Designerwahl. Ich würde diesen Teil der App nicht generisch machen. –

5

fand ich diese gemeinsam eine viel bessere Lösung speichern Funktion:

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

Immer wenn eine speichern Ihre NSManagedObjectContext es alle Änderungen zurückgesetzt Bedeutung wird Rollback fehlschlägt, die speichern im Kontext seit der letzten durchgeführt wurden . Sie müssen also sorgfältig aufpassen, Änderungen immer mit der oben genannten Speicherfunktion so früh und regelmäßig wie möglich zu machen, da Sie sonst leicht Daten verlieren könnten.

Für so dass andere Änderungen Einfügen von Daten könnte dies eine lockerere Variante sein leben:

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

Anmerkung: Ich bin mit CocoaLumberjack hier für die Anmeldung.

Jeder Kommentar, wie dies zu verbessern ist mehr als willkommen!

BR Chris

+0

Ich bekomme merkwürdiges Verhalten, wenn ich versuche Rollback zu verwenden, um dies zu erreichen: http://stackoverflow.com/questions/34426719/why-isnsmanagedobjectcontextobjectsdidchangenotification-called-twice-with-the – malhal

+0

Ich benutze Undo statt jetzt – malhal

2

ich eine Swift-Version der sachdienliche Antwort von @JohannesFahrenkrug gemacht haben, die nützlich sein können:

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

Ich bin überrascht, niemand hier tatsächlich den Fehler Umgang mit dem wie es behandelt werden soll.Wenn Sie sich die Dokumentation ansehen, werden Sie sehen.

Typische Gründe für einen Fehler hier sind: * Das Gerät Platz heraus ist. * Der persistente Speicher ist aufgrund von Berechtigungen oder Datenschutz nicht verfügbar, wenn das Gerät gesperrt ist. * Der Store konnte nicht in die aktuelle Modellversion migriert werden. * Das übergeordnete Verzeichnis existiert nicht, kann nicht erstellt werden oder lässt Schreiben nicht zu.

Also, wenn ich einen Fehler finden, wenn die Kerndaten-Stack einrichten, tauschen ich die RootViewController von UIWindow und UI zeigen, die eindeutig dem Benutzer sagt, dass ihr Gerät voll sein könnte, oder ihre Sicherheitseinstellungen sind zu hoch für diese App funktioniert. Ich gebe ihnen auch einen "Erneut versuchen" -Button, damit sie versuchen können, das Problem zu beheben, bevor der Core-Datenstapel erneut versucht wird.

Zum Beispiel könnte der Benutzer etwas Speicherplatz freigeben, zu meiner App zurückkehren und die Schaltfläche "Erneut versuchen" drücken.

Behauptungen? "Ja wirklich?" Zu viele Entwickler im Raum!

Ich bin auch überrascht von der Anzahl der Online-Tutorials, die nicht erwähnen, wie eine Sicherungsoperation aus diesen Gründen auch fehlschlagen könnte. Sie müssen also sicherstellen, dass alle Ereignisse, die in Ihrer App gespeichert werden, fehlschlagen können, da das Gerät JUST THIS MINUTE mit Ihren Apps voll wird und Sie sparen.

+0

Bei dieser Frage geht es um das Speichern im Core Data-Stack. Es geht nicht darum, den Core Data Stack einzurichten. Aber ich stimme zu, dass sein Titel irreführend sein könnte und vielleicht geändert werden sollte. – valeCocoa

+0

Ich stimme @valeCocoa nicht zu. In der Post geht es eindeutig darum, wie man Fehler in der Produktion vermeidet. Schau es dir noch einmal an. –

+0

@roddanash was ich gesagt habe ... WtH! :) Schau dir deine Antwort noch einmal an. – valeCocoa