Frage iPhone Core Data "Produktion" Fehlerbehandlung


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

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 irgendwelche Beispiele dafür, wie Sie sollte füge es ein.

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

Danke im Voraus, Matt


76
2018-02-14 20:49


Ursprung


Antworten:


Niemand wird Ihnen den Produktionscode zeigen, da er zu 100% von Ihrer Anwendung abhängt und wo der Fehler auftritt.

Persönlich gebe ich eine assert-Anweisung dort ein, weil 99,9% der Zeit dieser Fehler in der Entwicklung auftreten wird und wenn Sie es dort reparieren, ist es höchst unwahrscheinlich, dass Sie es in der Produktion sehen werden.

Nach der Assertion würde ich dem Benutzer eine Warnung anzeigen, 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 generieren würde, den Sie hoffentlich später verwenden können, um das Problem zu finden.


32
2018-02-14 21:14



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

Genießen.


31
2017-08-18 10:04



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

Jedes Mal, wenn ein Speichervorgang fehlschlägt, wird Ihr NSManagedObjectContext zurückgesetzt, was bedeutet, dass alle Änderungen zurückgesetzt werden, die in dem Kontext seit dem letzten Speichern ausgefü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 das Einfügen von Daten könnte dies eine losere Variante sein, die es anderen Änderungen erlaubt weiter zu 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;
}

Hinweis: Ich verwende CocoaLumberjack zum Loggen hier.

Jeder Kommentar zur Verbesserung ist mehr als willkommen!

BR Chris


5
2017-09-03 12:05



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

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

Wenn ich also einen Fehler bei der Einrichtung des Core Data Stacks finde, vertausche ich den rootViewController von UIWindow und zeige UI, die dem Benutzer klar sagt, dass sein Gerät möglicherweise voll ist oder die Sicherheitseinstellungen für diese App zu hoch sind. 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 ein Speichervorgang auch aus diesen Gründen 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.


4
2018-06-24 09:32



Ich habe eine schnelle Version der nützlichen Antwort von @JohannesFahrenkrug gemacht, 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
2017-08-25 07:35