Як я можу використовувати NSError у своєму додатку iPhone?


228

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

Чи міг би хтось навести приклад того, як я заповнюю потім використання NSError?

Відповіді:


473

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

Приклад:

- (id) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
    // begin feeding the world's children...
    // it's all going well until....
    if (ohNoImOutOfMonies) {
        // sad, we can't solve world hunger, but we can let people know what went wrong!
        // init dictionary to be used to populate error object
        NSMutableDictionary* details = [NSMutableDictionary dictionary];
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        // populate the error object with the details
        *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
        // we couldn't feed the world's children...return nil..sniffle...sniffle
        return nil;
    }
    // wohoo! We fed the world's children. The world is now in lots of debt. But who cares? 
    return YES;
}

Потім ми можемо використовувати такий метод. Навіть не турбуйтеся перевіряти об’єкт помилки, якщо метод не поверне нуль:

// initialize NSError object
NSError* error = nil;
// try to feed the world
id yayOrNay = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!yayOrNay) {
   // inspect error
   NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.

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

Найкраще місце для отримання додаткової інформації - документація Apple . Це дійсно добре.

Також є приємний, простий підручник на тему « Какао - моя подруга» .


37
це приклад смішна, коли - або
міна Yeow

це досить приголомшлива відповідь, хоча в ARC є і деякі проблеми, що передаються idна a BOOL. Будемо дуже вдячні за будь-які незначні зміни, сумісні з ARC.
NSTJ

6
@TomJowett Я був би дуже розлючений, якщо ми не зможемо покінчити з голодом у світі просто тому, що Apple підштовхнула нас до переходу до новішого світу ARC.
Манав

1
тип повернення може бути BOOL. Повернення NOу разі помилки, а замість того, щоб перевіряти значення повернення, просто перевірити error. Якщо nilйти вперед, якщо != nilвпоратися.
Габріеле Петронелла

8
-1: Вам дійсно потрібно включити код, який підтверджує, що **errorце не нуль. Інакше програма видасть помилку, яка є абсолютно недружньою і не дає зрозуміти, що відбувається.
FreeAsInBeer

58

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

У публікаціях вище вже пояснюється, як створити об'єкти NSError та повернути їх, тому я не буду заважати цією частиною. Я просто спробую запропонувати хороший спосіб інтегрувати помилки (коди, повідомлення) у власному додатку.


Я рекомендую створити 1 заголовок, який буде оглядом всіх помилок вашого домену (тобто програми, бібліотеки тощо). Мій поточний заголовок виглядає так:

FSError.h

FOUNDATION_EXPORT NSString *const FSMyAppErrorDomain;

enum {
    FSUserNotLoggedInError = 1000,
    FSUserLogoutFailedError,
    FSProfileParsingFailedError,
    FSProfileBadLoginError,
    FSFNIDParsingFailedError,
};

FSError.m

#import "FSError.h" 

NSString *const FSMyAppErrorDomain = @"com.felis.myapp";

Тепер, використовуючи вищезазначені значення для помилок, Apple створить основне стандартне повідомлення про помилку для вашої програми. Помилка може бути створена таким чином:

+ (FSProfileInfo *)profileInfoWithData:(NSData *)data error:(NSError **)error
{
    FSProfileInfo *profileInfo = [[FSProfileInfo alloc] init];
    if (profileInfo)
    {
        /* ... lots of parsing code here ... */

        if (profileInfo.username == nil)
        {
            *error = [NSError errorWithDomain:FSMyAppErrorDomain code:FSProfileParsingFailedError userInfo:nil];            
            return nil;
        }
    }
    return profileInfo;
}

Стандартне повідомлення про помилку, створене Apple ( error.localizedDescription) для наведеного вище коду, виглядатиме так:

Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"

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

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


1) створити stringsфайл, який буде містити помилки. Файли рядків легко локалізувати. Файл може мати такий вигляд:

FSError.strings

"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."

2) Додайте макроси для перетворення цілих кодів у локалізовані повідомлення про помилки. Я використовував 2 макроси у файлі Constannts + Macros.h. Я завжди включаю цей файл у заголовок префікса ( MyApp-Prefix.pch) для зручності.

Константи + макрос

// error handling ...

#define FS_ERROR_KEY(code)                    [NSString stringWithFormat:@"%d", code]
#define FS_ERROR_LOCALIZED_DESCRIPTION(code)  NSLocalizedStringFromTable(FS_ERROR_KEY(code), @"FSError", nil)

3) Тепер легко показати зручне повідомлення про помилку на основі коду помилки. Приклад:

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 
            message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code) 
            delegate:nil 
            cancelButtonTitle:@"OK" 
            otherButtonTitles:nil];
[alert show];

9
Чудова відповідь! Але чому б не помістити локалізований опис у словник інформації про користувачів, куди він належить? [Помилка NSErrorWithDomain: FSMyAppErrorDomain код: FSProfileParsingFailedError userInfo: @ {NSLocalizedDescriptionKey: FS_ERROR_LOCALIZED_DESCRIPTION (код помилки)}];
Річард Венебл

1
Чи є якесь конкретне місце, куди я повинен розмістити файл рядків? Від FS_ERROR_LOCALIZED_DESCRIPTION () я отримую лише номер (код помилки).
huggie

@huggie: не дуже впевнений, що ти маєш на увазі. Як правило, ці макроси, які я використовую у всьому додатку, я розміщую у файлі під назвою Constants+Macros.hта імпортую цей файл у заголовок префікса ( .pchфайл), щоб він був доступний скрізь. Якщо ви хочете сказати, що ви використовуєте лише 1 з 2 макросів, це може працювати. Можливо, перетворення з intна NSStringсправді не потрібне, хоча я цього не перевіряв.
Вольфганг Шреурс

@huggie: ой, я думаю, я тебе зараз розумію. Рядки повинні бути в локалізованому файлі ( .stringsфайлі), оскільки саме там буде виглядати макрос Apple. Про використання NSLocalizedStringFromTableтут читайте: developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Wolfgang Schreurs

1
@huggie: Так, я використовував локалізовані рядкові таблиці. Код у макросі FS_ERROR_LOCALIZED_DESCRIPTIONперевіряє локалізаційний рядок у файлі, який називається FSError.strings. Ви можете перевірити посібник з локалізації .stringsфайлів Apple, якщо вони вам не чужі.
Вольфганг Шреурс

38

Чудова відповідь Алекс. Одне потенційне питання - відпуск NULL. Посилання Apple на створення та повернення об'єктів NSError

...
[details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];

if (error != NULL) {
    // populate the error object with the details
    *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
}
// we couldn't feed the world's children...return nil..sniffle...sniffle
return nil;
...

30

Ціль-С

NSError *err = [NSError errorWithDomain:@"some_domain"
                                   code:100
                               userInfo:@{
                                           NSLocalizedDescriptionKey:@"Something went wrong"
                               }];

Швидкий 3

let error = NSError(domain: "some_domain",
                      code: 100,
                  userInfo: [NSLocalizedDescriptionKey: "Something went wrong"])

9

Будь ласка, зверніться до наступного підручника

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

Це дуже цікаве посилання, яке я знайшов нещодавно ErrorHandling


3

Я спробую підсумувати чудову відповідь Алекса та точку jlmendezbonini, додавши модифікацію, яка зробить все ARC сумісним (поки що це не так, оскільки ARC буде скаржитися, оскільки ви повинні повернутися id, що означає "будь-який об'єкт", але BOOLце не об'єкт тип).

- (BOOL) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
    // begin feeding the world's children...
    // it's all going well until....
    if (ohNoImOutOfMonies) {
        // sad, we can't solve world hunger, but we can let people know what went wrong!
        // init dictionary to be used to populate error object
        NSMutableDictionary* details = [NSMutableDictionary dictionary];
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        // populate the error object with the details
        if (error != NULL) {
             // populate the error object with the details
             *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
        }
        // we couldn't feed the world's children...return nil..sniffle...sniffle
        return NO;
    }
    // wohoo! We fed the world's children. The world is now in lots of debt. But who cares? 
    return YES;
}

Тепер замість того, щоб перевірити наявність зворотного значення нашого виклику методу, ми перевіряємо, чи errorіснує ще nil. Якщо це не так, у нас є проблеми.

// initialize NSError object
NSError* error = nil;
// try to feed the world
BOOL success = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!success) {
   // inspect error
   NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.

3
@Gabriela: Apple заявляє, що при використанні змінних непрямості для повернення помилок, сам метод завжди повинен мати деяке повернене значення у разі успіху чи невдачі. Apple закликає розробників спочатку перевірити наявність повернутого значення і лише в тому випадку, якщо повернене значення якимось чином недійсне, перевірити наявність помилок. Дивіться наступну сторінку: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
Wolfgang Schreurs

3

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

Скажімо, у нас визначені такі коди помилок:

typedef NS_ENUM(NSInteger, MyErrorCodes) {
    MyErrorCodesEmptyString = 500,
    MyErrorCodesInvalidURL,
    MyErrorCodesUnableToReachHost,
};

Ви б визначили свій метод, який може викликати помилку так:

- (void)getContentsOfURL:(NSString *)path success:(void(^)(NSString *html))success failure:(void(^)(NSError *error))failure {
    if (path.length == 0) {
        if (failure) {
            failure([NSError errorWithDomain:@"com.example" code:MyErrorCodesEmptyString userInfo:nil]);
        }
        return;
    }

    NSString *htmlContents = @"";

    // Exercise for the reader: get the contents at that URL or raise another error.

    if (success) {
        success(htmlContents);
    }
}

І тоді, коли ви його зателефонуєте, вам не потрібно турбуватися про декларування об'єкта NSError (завершення коду зробить це за вас) або перевірку повернутого значення. Ви можете просто поставити два блоки: один, який буде викликаний, коли є виняток, і той, який викликається, коли це вдасться:

[self getContentsOfURL:@"http://google.com" success:^(NSString *html) {
    NSLog(@"Contents: %@", html);
} failure:^(NSError *error) {
    NSLog(@"Failed to get contents: %@", error);
    if (error.code == MyErrorCodesEmptyString) { // make sure to check the domain too
        NSLog(@"You must provide a non-empty string");
    }
}];

0

Ну, це трохи поза сумнівом, але якщо у вас немає можливості для NSError, ви завжди можете відображати помилку низького рівня:

 NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]);

0
extension NSError {
    static func defaultError() -> NSError {
        return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
    }
}

який я можу використовувати, NSError.defaultError()коли у мене немає дійсного об'єкта помилок.

let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.