5 Frage: Fehlerbehandlung "Produktion" der iPhone-Kerndaten

Frage erstellt am Thu, Jan 22, 2015 12:00 AM

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

 
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 niemals Beispiele, wie Sie es implementieren sollten.

Hat jemand einen tatsächlichen "Produktions" -Code, der die obige Methode veranschaulicht (oder kann er mir in die Richtung weisen)?

Vielen Dank im Voraus, Matt

    
83
  1. + 1 Dies ist eine hervorragende Frage.
    2010-02-14 20: 58: 32Z
5 Antworten                              5                         

Niemand wird Ihnen den Produktionscode anzeigen, da dieser zu 100% von Ihrer Anwendung und dem Ort abhängt, an dem der Fehler auftritt.

Persönlich habe ich eine Assert-Anweisung eingefügt, da dieser Fehler in 99,9% der Fälle in der Entwicklung auftritt. Wenn Sie ihn dort beheben, ist es sehr unwahrscheinlich, dass Sie ihn in der Produktion sehen.

Nach der Bestätigung, dass dem Benutzer eine Warnung angezeigt wurde, teilen Sie ihm mit, dass ein nicht behebbarer Fehler aufgetreten ist und die Anwendung beendet wird. Sie können auch einen Klappentext einfügen und sie bitten, sich an den Entwickler zu wenden, damit Sie dies hoffentlich nachverfolgen können.

Danach würde ich abort () dort belassen, da es die App "zum Absturz bringen" und einen Stack-Trace generieren würde, den Sie hoffentlich später verwenden können, um das Problem aufzuspüren.

    
32
2010-02-14 21: 14: 42Z
  1. Marcus - Während Asserts in Ordnung sind, wenn Sie mit einer lokalen SQLite-Datenbank oder XML-Datei sprechen, benötigen Sie einen stabileren Fehlerbehandlungsmechanismus, wenn Ihr persistenter Speicher cloudbasiert ist.
    2013-04-29 19: 52: 56Z
  2. Wenn Ihr persistenter iOS Core Data-Speicher cloudbasiert ist, treten größere Probleme auf.
    2013-04-29 23: 34: 09Z
  3. Ich bin in einigen Punkten nicht mit Apple einverstanden. Es ist der Unterschied zwischen einer Unterrichtssituation (Apple) und in den Gräben (ich). Ja, aus akademischen Gründen sollten Sie Abbrüche entfernen. In Wirklichkeit sind sie nützlich, um Situationen einzufangen, die Sie sich nie für möglich gehalten haben. Autoren von Apple-Dokumentationen geben gerne vor, dass jede Situation rechenschaftspflichtig ist. 99,999% von ihnen sind. Was tun Sie für das wirklich Unerwartete? Ich stürze ab und erstelle ein Protokoll, um herauszufinden, was passiert ist. Dafür ist Abbruch gedacht.
    2014-08-06 16: 34: 10Z
  4. @ cschuff, keine davon wirkt sich auf einen Aufruf von Core Data -save: aus. All diese Bedingungen sind lange bevor Ihr Code diesen Punkt erreicht.
    03.09.2014 15: 57: 56Z
  5. Dies ist ein vorweggenommener Fehler, der vor dem Speichern abgefangen und behoben werden kann. Sie können Core Data fragen, ob die Daten gültig und richtig sind. Außerdem können Sie dies zum Zeitpunkt des Verbrauchs testen, um sicherzustellen, dass alle gültigen Felder vorhanden sind. Dies ist ein Fehler auf Entwicklerebene, der behoben werden kann, lange bevor die -save: aufgerufen wird.
    2014-09-04 07: 24: 44Z

Dies ist eine allgemeine Methode, die ich entwickelt habe, um Validierungsfehler auf dem iPhone zu behandeln und anzuzeigen. Aber Marcus hat recht: Wahrscheinlich möchten Sie die Nachrichten optimieren, um sie benutzerfreundlicher zu gestalten. Dies gibt Ihnen jedoch 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?:@""),(entityName?@": ":@""),msg];
            }
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Validation Error" 
                                                            message:messages
                                                           delegate:nil 
                                                  cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
            [alert show];
            [alert release];
        }
    }
}

Viel Spaß.

    
31
18.08.2010 10: 04: 29Z
  1. Mit diesem Code kann auf keinen Fall ein Fehler festgestellt werden. Sieht solide aus. Persönlich bevorzuge ich es, Core Data-Fehler mit einer Behauptung zu behandeln. Ich habe noch keinen gesehen, der es in die Produktion schafft, deshalb habe ich sie immer als Entwicklungsfehler und nicht als mögliche Produktionsfehler angesehen. Obwohl dies sicherlich ein anderes Schutzniveau ist:)
    18.08.2010 16: 38: 39Z
  2. Marcus, zu Behauptungen: Was halten Sie davon, den Code DRY im Hinblick auf Validierungen zu behalten? 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 diesem regulären Ausdruck übereinstimmen . Das sollte alle Informationen sein, die zum Anzeigen einer geeigneten Nachricht für den Benutzer erforderlich sind. Es ist irgendwie nicht gut für mich, diese Überprüfungen vor dem Speichern des MOC erneut im Code durchzuführen. Was denkst du?
    07.09.2010 12: 15: 58Z
  3. Ich habe diesen Kommentar nie gesehen, da er nicht in meiner Antwort enthalten war. Selbst wenn Sie die Validierung in das Modell einfügen, müssen Sie immer noch prüfen, ob das Objekt die Validierung bestanden hat, und dies dem Benutzer präsentieren. Je nach Design kann dies auf Feldebene (dieses Passwort ist falsch usw.) oder am Speicherort erfolgen. Designer Wahl. Ich würde diesen Teil der App nicht generisch machen.
    2013-01-15 18: 54: 04Z
  4. @ MarcusS.Zarra Ich schätze, du hast es nie bekommen, weil ich dich nicht richtig @ -nenne :) Ich denke, wir sind uns einig: Ich möchte die Validierung- Informationen , die im Modell enthalten sein sollen, aber die Entscheidung, wann die Validierung ausgelöst werden soll und wie das Validierungsergebnis behandelt und dargestellt werden soll, sollte nicht generisch sein und an den entsprechenden Stellen in erfolgen den Anwendungscode.
    2013-01-16 07: 53: 20Z
  5. Der Code sieht gut aus. Meine einzige Frage ist, ob ich nach dem Anzeigen der Warnung oder dem Protokollieren der Analyse den Core Data-Kontext zurücksetzen oder die App abbrechen soll. Andernfalls verursachen die nicht gespeicherten Änderungen wahrscheinlich weiterhin dasselbe Problem, wenn Sie versuchen, erneut zu speichern.
    2013-06-04 18: 17: 32Z

Ich bin überrascht, dass hier niemand den Fehler so behandelt, wie er behandelt werden soll. Wenn Sie sich die Dokumentation ansehen, sehen Sie.

  

Typische Gründe für einen Fehler sind hier: * Das Gerät ist ausgefallen   Raum. * Auf den permanenten Speicher kann aufgrund von nicht zugegriffen werden   Berechtigungen oder Datenschutz, wenn das Gerät gesperrt ist. * Das   Geschäft konnte nicht auf die aktuelle Modellversion migriert werden. * Das   Das übergeordnete Verzeichnis existiert nicht, kann nicht erstellt werden oder ist nicht zulässig   schreiben.

Wenn ich beim Einrichten des Core-Datenstapels einen Fehler finde, tausche ich den rootViewController von UIWindow aus und zeige der Benutzeroberfläche an, dass das Gerät möglicherweise voll ist oder die Sicherheitseinstellungen zu hoch sind, damit diese App funktioniert . Ich gebe ihnen auch die Schaltfläche "Erneut versuchen", damit sie versuchen können, das Problem zu beheben, bevor der Kerndatenstapel erneut versucht wird.

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

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

Ich bin auch überrascht von der Anzahl der Online-Tutorials, in denen nicht erwähnt wird, wie ein Speichervorgang auch aus diesen Gründen fehlschlagen kann. Sie müssen also sicherstellen, dass ein Speicherereignis ÜBERALL in Ihrer App fehlschlagen kann, da das Gerät NUR DIESE MINUTE mit dem Speichern von Apps voll ist.

    
7
2017-06-24 09: 32: 59Z
  1. Bei dieser Frage geht es um das Speichern im Core Data Stack, nicht um das Einrichten des Core Data Stack. Ich stimme jedoch zu, dass der Titel irreführend sein könnte und möglicherweise geändert werden sollte.
    2017-09-13 07: 47: 39Z
  2. Ich bin mit @valeCocoa nicht einverstanden.In der Post geht es eindeutig um den Umgang mit Sicherungsfehlern in der Produktion. Schauen Sie sich das noch einmal an.
    2017-09-14 08: 35: 02Z
  3. @ roddanash was ich gesagt habe ... WtH! :) Sehen Sie sich Ihre Antwort noch einmal an.
    2017-09-14 13: 16: 37Z
  4. Du bist verrückt, Bruder
    2017-09-14 19: 09: 25Z
  5. Sie fügen einen Teil der Dokumentation für die Fehler ein, die beim Instanziieren des beständigen Speichers auftreten können, und stellen eine Frage zu den Fehlern, die beim Speichern des Kontexts auftreten, und ich bin der Verrückte ein? Ok ...
    2017-09-15 12: 35: 52Z

Ich fand diese allgemeine Speicherfunktion viel besser:

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

Wenn ein Speichervorgang fehlschlägt, wird Ihr NSManagedObjectContext zurückgesetzt. Dies bedeutet, dass alle Änderungen zurückgesetzt werden, die im Kontext seit dem letzten Speichervorgang vorgenommen wurden. Sie müssen also vorsichtig sein, um Änderungen mit der oben genannten Speicherfunktion immer so früh und regelmäßig wie möglich zu speichern, da Sie sonst leicht Daten verlieren können.

Zum Einfügen von Daten ist dies möglicherweise eine lockerere Variante, von der andere Änderungen übernommen werden können:

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

Hinweis: Ich verwende CocoaLumberjack, um mich hier anzumelden.

Jeder Kommentar zur Verbesserung ist mehr als willkommen!

BR Chris

    
5
2015-12-23 01: 35: 13Z
  1. Ich erhalte ein seltsames Verhalten, wenn ich versuche, das Rollback zu verwenden, um dies zu erreichen: stackoverflow.com/questions/34426719/…
    aufgerufen
    2015-12-23 01: 36: 33Z
  2. Ich verwende jetzt stattdessen "Rückgängig"
    2016-09-12 20: 55: 53Z

Ich habe eine schnelle Version der nützlichen Antwort von @JohannesFahrenkrug erstellt, die nützlich sein kann:

 
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"
}`
    
2
2016-08-25 07: 35: 01Z
Quelle platziert Hier