Fehlerbehandlung bei der Produktion von iPhone-Kerndaten


84

Ich habe in dem von Apple bereitgestellten Beispielcode gesehen, wie Sie mit Core Data-Fehlern umgehen sollen. Dh:

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 dafür, wie Sie es implementieren sollten .

Hat jemand einen tatsächlichen "Produktions" -Code, der die obige Methode veranschaulicht (oder kann er mich darauf hinweisen)?

Vielen Dank im Voraus, Matt


7
+1 das ist eine ausgezeichnete Frage.
Dave DeLong

Antworten:


32

Niemand wird Ihnen Produktionscode anzeigen, da dieser zu 100% von Ihrer Anwendung abhängt und davon, wo der Fehler auftritt.

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

Nach der Bestätigung würde ich dem Benutzer eine Warnung anzeigen, ihn wissen lassen, dass ein nicht behebbarer Fehler aufgetreten ist und dass die Anwendung beendet wird. Sie können dort 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 bringt" und einen Stack-Trace generiert, den Sie hoffentlich später verwenden können, um das Problem aufzuspüren.


Marcus - Während Asserts in Ordnung sind, wenn Sie mit einer lokalen SQLite-Datenbank oder XML-Datei sprechen, benötigen Sie einen robusteren Fehlerbehandlungsmechanismus, wenn Ihr persistenter Speicher Cloud-basiert ist.
dar512

4
Wenn Ihr persistenter iOS Core Data-Speicher Cloud-basiert ist, haben Sie größere Probleme.
Marcus S. Zarra

3
Ich bin mit Apple in einer Reihe von Themen nicht einverstanden. Es ist der Unterschied zwischen einer Unterrichtssituation (Apple) und in den Gräben (ich). Ja, aus einer akademischen Situation sollten Sie Abbrüche entfernen. In Wirklichkeit sind sie nützlich, um Situationen zu erfassen, die Sie sich nie für möglich gehalten haben. Autoren von Apple-Dokumentationen geben gerne vor, dass jede Situation verantwortlich ist. 99,999% von ihnen sind. Was machst du für das wirklich Unerwartete? Ich stürze ab und erstelle ein Protokoll, damit ich herausfinden kann, was passiert ist. Dafür ist Abbruch da.
Marcus S. Zarra

1
@cschuff, keine davon wirkt sich auf einen Kerndatenaufruf aus -save:. All diese Bedingungen treten auf, lange bevor Ihr Code diesen Punkt erreicht.
Marcus S. Zarra

3
Dies ist ein erwarteter Fehler, der vor dem Speichern abgefangen und korrigiert werden kann. Sie können Core Data fragen, ob die Daten gültig sind, und sie korrigieren. 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 lange vor dem -save:Aufruf von behandelt werden kann.
Marcus S. Zarra

32

Dies ist eine generische Methode, mit der ich Validierungsfehler auf dem iPhone behandeln und anzeigen konnte. Aber Marcus hat Recht: Sie möchten wahrscheinlich die Nachrichten optimieren, um benutzerfreundlicher zu sein. 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];
        }
    }
}

Genießen.


3
Mit diesem Code kann man sicher nichts falsch machen. 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 geschafft hat, daher habe ich sie immer als Entwicklungsfehler und nicht als potenzielle Produktionsfehler angesehen. Obwohl dies sicherlich eine andere Schutzstufe ist :)
Marcus S. Zarra

2
Marcus über Behauptungen: Wie ist Ihre Meinung dazu, Code in Bezug auf Validierungen trocken 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 diesem regulären Ausdruck übereinstimmen . Dies sollten alle Informationen sein, die erforderlich sind, um dem Benutzer eine entsprechende Nachricht anzuzeigen. Es passt irgendwie nicht zu mir, diese Überprüfungen erneut im Code durchzuführen, bevor das MOC gespeichert wird. Was denken Sie?
Johannes Fahrenkrug

2
Ich habe diesen Kommentar nie gesehen, da er nicht auf meiner Antwort stand. Selbst wenn Sie eine Validierung in das Modell einfügen, müssen Sie überprü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 Speicherpunkt sein. Wahl des Designers. Ich würde diesen Teil der App nicht generisch machen.
Marcus S. Zarra

1
@ MarcusS.Zarra Ich denke , man es nie dazu gekommen , weil ich nicht richtig @ tat -mention Sie :) Ich denke , dass wir voll und ganz zustimmen: Ich habe die Validierungs- möchte Informationen im Modell sein, aber die Entscheidung , wenn sie Trigger - Validierung und Die Behandlung und Darstellung des Validierungsergebnisses sollte nicht generisch sein und an den entsprechenden Stellen im Anwendungscode erfolgen.
Johannes Fahrenkrug

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 werden die nicht gespeicherten Änderungen wahrscheinlich das gleiche Problem verursachen, wenn Sie erneut versuchen, sie zu speichern.
Jake

6

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

Typische Gründe für einen Fehler sind: * Das Gerät hat nicht genügend Platz. * Auf den persistenten Speicher kann aufgrund von Berechtigungen oder Datenschutz beim Sperren des Geräts nicht zugegriffen werden. * Der Store konnte nicht auf die aktuelle Modellversion migriert werden. * Das übergeordnete Verzeichnis existiert nicht, kann nicht erstellt werden oder verbietet das Schreiben.

Wenn ich also beim Einrichten des Kerndatenstapels einen Fehler finde, tausche ich den rootViewController von UIWindow aus und zeige eine Benutzeroberfläche, die dem Benutzer deutlich macht, dass sein 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.

Behauptungen? "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 Ihren Apps voll ist. Speichern Speichern Speichern Speichern.


Bei dieser Frage geht es um das Speichern im Core Data Stack, nicht um das Einrichten des Core Data Stack. Aber ich stimme zu, dass der Titel irreführend sein könnte und vielleicht geändert werden sollte.
ValeCocoa

Ich bin nicht einverstanden mit @valeCocoa. In diesem Beitrag geht es klar darum, wie man mit Speicherfehlern in der Produktion umgeht. Schau noch einmal hin.

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

Du bist verrückt, Bruder

Sie fügen einen Teil der Dokumentation für die Fehler ein, die beim Instanziieren des persistenten Speichers auftreten können, und stellen eine Frage zu den Fehlern, die beim Speichern des Kontexts auftreten, und ich bin der Verrückte? Ok…
valeCocoa

5

Ich fand diese allgemeine Speicherfunktion eine viel bessere Lösung:

- (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, was bedeutet, dass alle Änderungen zurückgesetzt werden, die seit dem letzten Speichern im Kontext vorgenommen wurden . Sie müssen also sorgfältig aufpassen, um Änderungen mit der oben genannten Speicherfunktion immer so früh und regelmäßig wie möglich beizubehalten, da Sie sonst leicht Daten verlieren könnten.

Zum Einfügen von Daten kann dies eine lockerere Variante sein, mit der andere Änderungen weitergeführt 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 für die Protokollierung hier.

Jeder Kommentar zur Verbesserung ist mehr als willkommen!

BR Chris


Ich bekomme ein seltsames Verhalten, wenn ich versuche, Rollback zu verwenden, um dies zu erreichen: stackoverflow.com/questions/34426719/…
malhal

Ich benutze stattdessen jetzt rückgängig machen
malhal

2

Ich habe eine Swift-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"
}`
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.