Обробка помилок "Виробництво" основних даних iPhone


84

Я бачив у прикладі коду, наданого Apple, посилання на те, як слід обробляти помилки основних даних. Тобто:

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

Але ніколи жодних прикладів того, як ви повинні це реалізувати.

Хто-небудь має (або може вказати мені в бік) якийсь фактичний "виробничий" код, який ілюструє вищезазначений метод.

Заздалегідь спасибі, Метт


7
+1 це відмінне запитання.
Dave DeLong

Відповіді:


32

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

Особисто я ставлю заяву Assert там , тому що 99,9% часу ця помилка буде виникати в розвитку і коли ви фіксуєте його там дуже малоймовірно , що ви будете бачити його у виробництво.

Після затвердження я подаю попередження користувачеві, повідомим його про невиправлену помилку та про те, що програма збирається вийти. Ви також можете додати туди розмиття, попросивши їх зв’язатися з розробником, щоб ви могли сподіватися, що це зроблено.

Після цього я залишив би abort () там, оскільки він "виведе з ладу" програму і сформує стек стека, який можна сподіватися використати пізніше для відстеження проблеми.


Маркус - Хоча твердження добре, якщо ви розмовляєте з локальною базою даних sqlite або XML-файлом, вам потрібен більш надійний механізм обробки помилок, якщо ваш постійний магазин базується на хмарі.
dar512

4
Якщо ваш постійний магазин iOS Core Data працює на основі хмари, у вас виникають більші проблеми.
Marcus S. Zarra

3
Я не згоден з Apple щодо ряду тем. Це різниця між ситуацією викладання (Apple) та в окопах (я). З академічної ситуації, так, вам слід видалити аборти. Насправді вони корисні для лову ситуацій, які ви ніколи не уявляли можливими. Автори документації від Apple люблять робити вигляд, що кожна ситуація відповідальна. З них 99,999%. Що ви робите для справді несподіваного? Я аварійно завершую роботу і згенерую журнал, щоб я міг з’ясувати, що сталося. Саме для цього призначений аборт.
Маркус С. Зарра,

1
@cschuff, жоден з них не впливає на основний -save:дзвінок даних . Усі ці умови відбуваються задовго до того, як ваш код досягне цієї точки.
Marcus S. Zarra

3
Це передбачувана помилка, яку можна вловити та виправити перед збереженням. Ви можете запитати основні дані, чи є дані дійсними, і виправити їх. Крім того, ви можете перевірити це під час споживання, щоб переконатися, що присутні всі дійсні поля. Це помилка рівня розробника, яку можна усунути задовго до -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
Звичайно, я не бачу нічого поганого в цьому коді. Виглядає солідно. Особисто я вважаю за краще обробляти помилки основних даних із твердженням. Я ще не бачив, щоб хтось дійшов до виробництва, тому я завжди вважав їх помилками в розробці, а не потенційними помилками виробництва. Хоча це, безумовно, інший рівень захисту :)
Маркус С. Зарра,

2
Маркус, про твердження: Якою є ваша думка щодо збереження коду СУХИМ з точки зору перевірки? На мою думку, дуже бажано визначити свої критерії перевірки лише один раз, у моделі (де вона належить): Це поле не може бути порожнім, це поле має бути принаймні 5 символів, і це поле має відповідати цьому регулярному виразу . Це повинна бути вся інформація, необхідна для відображення відповідної повідомлення користувачеві. Мені якось не годиться робити ці перевірки ще раз у коді перед збереженням MOC. Що ти думаєш?
Йоханнес Фаренкруг,

2
Ніколи не бачив цього коментаря, оскільки його не було на мою відповідь. Навіть коли ви ставите перевірку в модель, вам все одно потрібно перевірити, чи не пройшов об'єкт перевірку та представити це користувачеві. Залежно від дизайну, який може бути на рівні поля (цей пароль поганий тощо) або в точці збереження. Вибір дизайнера. Я б не робив цю частину програми загальною.
Marcus S. Zarra

1
@ MarcusS.Zarra Я думаю, ви ніколи цього не отримали, тому що я неправильно @ -згадав вас :) Я думаю, ми повністю погоджуємось: я хотів би, щоб інформація про перевірку була в моделі, але рішення коли запускати перевірку та як обробляти та представляти результат перевірки, не повинно бути загальним та оброблятись у відповідних місцях коду програми.
Йоханнес Фаренкруг,

Код виглядає чудово. Моє єдине питання, після показу попередження або реєстрації аналізу, чи слід відміняти контекст основних даних або скасувати програму? В іншому випадку, я припускаю, що незбережені зміни продовжуватимуть спричиняти ту саму проблему при спробі зберегти знову.
Джейк

6

Я здивований, що тут ніхто насправді не обробляє помилку так, як вона призначена. Якщо ви подивитесь на документацію, то побачите.

Типові причини помилки тут включають: * У пристрої не вистачає місця. * Постійне сховище недоступне через дозволи або захист даних, коли пристрій заблоковано. * Магазин не вдалося перенести на поточну версію моделі. * Батьківський каталог не існує, не може бути створений або забороняє запис.

Отже, якщо я виявляю помилку під час налаштування основного стеку даних, я міняю місцями rootViewController UIWindow і показую користувальницький інтерфейс, який чітко повідомляє користувачеві, що їх пристрій може бути заповненим, або налаштування безпеки занадто високі, щоб цей додаток функціонував. Я також даю їм кнопку "повторити спробу", щоб вони могли спробувати вирішити проблему до повторної спроби стека основних даних.

Наприклад, користувач міг звільнити місце для зберігання, повернутися до мого додатка та натиснути кнопку повторити спробу.

Стверджує? Справді? У кімнаті забагато розробників!

Я також здивований кількістю навчальних посібників в Інтернеті, в яких не згадується, як операція збереження також може провалитися з цих причин. Отже, вам потрібно буде переконатись, що будь-яка подія збереження БЕЗ КУДИ у вашому додатку може вийти з ладу, оскільки пристрій ТОЛЬКО ЦЬОЇ ХВИЛИНИ заповнився завдяки збереженню заощаджень, що заощаджують ваші програми.


Це питання стосується збереження в стеку основних даних, а не налаштування стеку основних даних. Але я згоден, що його назва може ввести в оману, і, можливо, її слід змінити.
valeCocoa

Я не згоден з @valeCocoa. У цій публікації чітко йдеться про те, як обробляти помилки у збереженні виробництва. Погляньте ще раз.

@roddanash, що я сказав ... WtH! :) Погляньте ще раз на свою відповідь.
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 Кріс


Я отримую дивна поведінка , коли я намагаюся використовувати відкат , щоб досягти цього: stackoverflow.com/questions/34426719 / ...
malhal

Замість цього я використовую скасування
malhal

2

Я зробив швидку версію корисної відповіді @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"
}`
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.