Обработка ошибок «Производство» основных данных iPhone

85

Я видел в примере кода, предоставленном Apple, ссылки на то, как следует обрабатывать ошибки Core Data. Т.е.:

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

Но ни разу никаких примеров того, как это следует реализовать.

Есть ли у кого-нибудь (или может указать мне направление) некий реальный «производственный» код, который иллюстрирует вышеуказанный метод.

Заранее спасибо, Мэтт

Sway
источник
8
+1 это отличный вопрос.
Дэйв Делонг,

Ответы:

33

Никто не собирается показывать вам производственный код, потому что он на 100% зависит от вашего приложения и места возникновения ошибки.

Лично я ставлю заявление Assert там , потому что 99,9% времени эта ошибка будет возникать в развитии и когда вы фиксируете его там весьма маловероятно , что вы будете видеть его в производство.

После утверждения я бы представил пользователю предупреждение, чтобы он знал, что произошла неисправимая ошибка и что приложение собирается выйти. Вы также можете разместить там рекламное объявление с просьбой связаться с разработчиком, чтобы вы могли отслеживать, как это было сделано.

После этого я бы оставил abort () там, так как он "вылетит" приложение и сгенерирует трассировку стека, которую вы, надеюсь, сможете использовать позже, чтобы отследить проблему.

Маркус С. Зарра
источник
Маркус - Хотя утверждения подходят, если вы разговариваете с локальной базой данных sqlite или XML-файлом, вам нужен более надежный механизм обработки ошибок, если ваше постоянное хранилище основано на облаке.
dar512
4
Если ваше постоянное хранилище основных данных iOS является облачным, у вас есть более серьезные проблемы.
Marcus S. Zarra
3
Я не согласен с Apple по ряду вопросов. Это разница между ситуацией обучения (Apple) и ситуацией в окопах (я). С академической точки зрения да, вам следует удалить аборты. На самом деле они полезны, чтобы поймать ситуации, которые вы даже представить себе не могли. Авторы документации Apple любят делать вид, что за каждую ситуацию можно подотчетно. 99,999% из них есть. Что вы делаете для действительно неожиданного? Я выхожу из строя и создаю журнал, чтобы узнать, что произошло. Вот для чего нужен прерывание.
Маркус С. Зарра
1
@cschuff, ни один из них не влияет на -save:вызов основных данных . Все эти условия возникают задолго до того, как ваш код достигнет этой точки.
Маркус С. Зарра
3
Это ожидаемая ошибка, которую можно обнаружить и исправить до сохранения. Вы можете спросить Core Data, действительны ли данные, и исправить их. Кроме того, вы можете проверить это во время использования, чтобы убедиться, что присутствуют все допустимые поля. Это ошибка уровня разработчика, с которой можно справиться задолго до -save:вызова.
Маркус С. Зарра
32

Это один из общих методов, которые я придумал для обработки и отображения ошибок проверки на 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?:@""),(entityName?@": ":@""),msg];
            }
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Validation Error" 
                                                            message:messages
                                                           delegate:nil 
                                                  cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
            [alert show];
            [alert release];
        }
    }
}

Наслаждаться.

Йоханнес Фаренкруг
источник
3
Конечно, не вижу ничего плохого в этом коде. Смотрится солидно. Лично я предпочитаю обрабатывать ошибки Core Data с помощью утверждения. Я еще не видел, чтобы один из них попал в производство, поэтому я всегда считал их ошибками разработки, а не потенциальными производственными ошибками. Хотя это, безусловно, другой уровень защиты :)
Маркус С. Зарра
2
Маркус, насчет утверждений: что вы думаете о том, чтобы код оставался СУХИМ с точки зрения валидации? На мой взгляд, очень желательно определять критерии проверки только один раз в модели (где оно принадлежит): это поле не может быть пустым, это поле должно содержать не менее 5 символов, и это поле должно соответствовать этому регулярному выражению . Это должна быть вся информация, необходимая для отображения пользователю соответствующего сообщения. Мне почему-то не нравится повторять эти проверки в коде перед сохранением MOC. Что вы думаете?
Йоханнес Фаренкруг
2
Никогда не видел этот комментарий, так как его не было в моем ответе. Даже когда вы вводите валидацию в модель, вам все равно необходимо проверить, прошел ли объект валидацию, и представить ее пользователю. В зависимости от дизайна это может быть на уровне поля (неверный пароль и т. Д.) Или в точке сохранения. Выбор дизайнера. Я бы не стал делать эту часть приложения универсальной.
Маркус С. Зарра
1
@ MarcusS.Zarra Думаю, вы так и не получили его, потому что я неправильно @-упомянул вас :) Думаю, мы полностью согласны: я бы хотел, чтобы информация о валидации была в модели, но решение, когда запускать валидацию и способы обработки и представления результата проверки не должны быть общими и должны обрабатываться в соответствующих местах кода приложения.
Йоханнес Фаренкруг
Код выглядит отлично. Мой единственный вопрос: после отображения предупреждения или регистрации анализа следует откатить контекст Core Data или прервать приложение? В противном случае я предполагаю, что несохраненные изменения будут продолжать вызывать ту же проблему при повторной попытке сохранения.
Jake
6

Я удивлен, что здесь никто не обрабатывает ошибку так, как задумано. Если вы посмотрите документацию, то увидите.

Типичные причины ошибки здесь: * На устройстве закончилось место. * Постоянное хранилище недоступно из-за разрешений или защиты данных, когда устройство заблокировано. * Не удалось перенести магазин на текущую версию модели. * Родительский каталог не существует, не может быть создан или запрещает запись.

Поэтому, если я обнаруживаю ошибку при настройке основного стека данных, я заменяю rootViewController UIWindow и показываю пользовательский интерфейс, который четко сообщает пользователю, что его устройство может быть заполнено или его настройки безопасности слишком высоки для работы этого приложения. Я также даю им кнопку «Повторить попытку», чтобы они могли попытаться исправить проблему до того, как будет выполнена повторная попытка основного стека данных.

Например, пользователь может освободить место для хранения, вернуться в мое приложение и нажать кнопку «Повторить попытку».

Утверждает? В самом деле? Слишком много разработчиков в комнате!

Я также удивлен количеством онлайн-руководств, в которых не упоминается, как операция сохранения может завершиться неудачно по этим причинам. Таким образом, вам нужно будет убедиться, что любое событие сохранения в ЛЮБОМ месте в вашем приложении может завершиться ошибкой, потому что устройство ТОЛЬКО В ЭТУ МИНУТУ было заполнено вашими приложениями, сохраняющими сохранение.


источник
Этот вопрос касается сохранения в стеке Core Data, а не о настройке стека Core Data. Но я согласен, что его название может вводить в заблуждение и, возможно, его следует изменить.
valeCocoa
Я не согласен с @valeCocoa. Сообщение явно о том, как обрабатывать ошибки сохранения в производственной среде. Взгляните еще раз.
@roddanash, вот что я сказал ... ЧТО! :) Взгляните еще раз на свой ответ.
valeCocoa
Ты сумасшедший, братан
вы вставляете часть документации об ошибках, которые могут возникнуть при создании экземпляра постоянного хранилища, на вопрос об ошибках, возникающих при сохранении контекста, и я сумасшедший? Хорошо…
valeCocoa
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 Крис

cschuff
источник
У меня странное поведение, когда я пытаюсь использовать откат для достижения этой цели: stackoverflow.com/questions/34426719/…
malhal
Вместо этого я использую отмену
malhal
2

Я сделал Swift-версию полезного ответа @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"
}`
cdescours
источник