кидання винятку в Objective-c / какао


Відповіді:


528

Я використовую [NSException raise:format:]наступне:

[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];

9
Я віддаю перевагу такому способу, як прихильному до @throw([NSException exceptionWith…])підходу, оскільки більш стислому.
Сем Соффс

9
Обов'язково прочитайте важливу обмовку від шкоди ( stackoverflow.com/questions/324284/324805#324805 )
e.James

26
Я, як правило, теж віддаю перевагу цьому, але є одна готча. Можливо, це буде моя поточна версія Xcode, але синтаксис [NSException підвищення ...], схоже, не розпізнається аналізатором як шлях виходу з методу, який повертає значення. Я бачу застереження "Керування може досягти кінця недійсної функції" при використанні цього синтаксису, але за допомогою синтаксису @throw ([NSException изключення з…]) парсер розпізнає це як вихід і не відображає попередження.
mattorb

1
@mpstx Я завжди використовую синтаксис кидка з тієї причини, яку ви вказали (що все ще актуально через два роки в Xcode 4.6, і, ймовірно, завжди буде). Якщо IDE визнає, що викидання виключення є точкою виходу з функції, часто має значення, якщо ви хочете уникати попереджень.
Марк Амері

FWIW Я зауважую, що блоки @ try / @ catch також призводять до помилково негативних попереджень попереджень "контроль доходить до кінця недійсної функції" (тобто попередження не відображається, коли воно повинно бути)
Брайан Герстл,

256

Тут слово обережності. У Objective-C, на відміну від багатьох подібних мов, вам, як правило, слід намагатися уникати використання виключень для поширених ситуацій помилок, які можуть виникати в звичайній роботі.

Документація Apple для Obj-C 2.0 зазначає наступне: "Важливо: Винятки є ресурсомісткими в" Objective-C ". Ви не повинні використовувати винятки для загального контролю потоку або просто для позначення помилок (таких як файл недоступний)"

Концептуальна документація по обробці винятків Apple пояснює те саме, але більше слів: "Важливо. Ви повинні зарезервувати використання виключень для програмування або несподіваних помилок виконання, таких як позабіржовий доступ до колекції, спроби мутувати незмінні об'єкти, надсилаючи недійсне повідомлення і втрачаєш з'єднання з віконним сервером. Ти зазвичай берешся за подібні помилки, за винятком випадків, коли програма створюється, а не під час виконання. [.....] Замість винятків, об'єкти помилок (NSError) та Механізм доставки помилок какао є рекомендованим способом повідомлення очікуваних помилок у програмах какао ".

Причини цього частково полягають у дотриманні ідіом програмування в Objective-C (використання повернених значень у простих випадках та посилальних параметрів (найчастіше клас NSError) у складніших випадках), частково те, що кидання та вилучення винятків набагато дорожче та нарешті (і найважливіше перебирає), що винятки Objective-C - це тонка обгортка навколо функцій setjmp () і longjmp () C, яка по суті псує ваше обережне управління пам’яттю, див. це пояснення .


11
Я думаю, що це стосується більшості мов програмування: "намагайтеся уникати винятків для поширених ситуацій помилок". Те саме стосується і Java; погана практика обробляти помилки введення користувача (наприклад) за винятками. Не тільки завдяки використанню ресурсів, а й для чіткості коду.
beetstra

6
Що ще важливіше, винятки з какао призначені для позначення програмних помилок, які не підлягають відшкодуванню. В іншому випадку це суперечить рамкам і може призвести до невизначеної поведінки. Докладніше див. Stackoverflow.com/questions/3378696/iphone-try-end-try/… .
КПМ

9
"Те саме стосується Java;" Не згоден. Ви можете використовувати перевірені винятки на Java просто для нормальних умов помилок. Звичайно, ви не будете використовувати винятки під час виконання програми.
Даніель Райан

Я віддаю перевагу способу какао (винятки стосуються лише помилок програміста), тому я вважаю за краще це робити і на Java, але реальність полягає в тому, що вам слід користуватися типовою практикою в оточенні, а винятки для обробки помилок стикаються як погано пахне в Objective-C, але для цієї мети на Java використовується дуже багато.
gnasher729

1
Цей коментар не відповідає на запитання. Можливо, ОП просто хоче збити додаток, щоб перевірити, чи працює система звітів про аварійну ситуацію, як очікувалося.
Симон

62
@throw([NSException exceptionWith…])

Xcode розпізнає @throwоператори як точки виходу з функції, як returnоператори. Використання @throwсинтаксису дозволяє уникнути помилкових попереджень " Керування може дійти до кінця недійсної функції " [NSException raise:…].

Також @throwможна використовувати для метання об'єктів, які не належать до класу NSException.


11
@Steph Thirion: Див. Developer.apple.com/documentation/Cocoa/Conceptual/Exceptions/… для всіх деталей. Нижня лінія? Обидва будуть працювати, але @throw можна використовувати для викидання об'єктів, які не належать до класу NSException.
e.James

33

Що стосується [NSException raise:format:]. Для тих, хто походить з фону Java, ви згадаєте, що Java розрізняє виняток і RuntimeException. Виняток - це перевірений виняток, а RuntimeException не встановлено. Зокрема, Java пропонує використовувати перевірені винятки для "нормальних умов помилок" та неперевірені винятки для "помилок виконання, викликаних помилкою програміста". Схоже, винятки Objective-C повинні використовуватися в тих самих місцях, де ви б використовували неперевірений виняток, а значення повернення коду помилки або значення NSError є кращими в тих місцях, де ви б використовували перевірене виключення.


1
Так, це правильно (хоча через 4 роки: D), створіть свій власний клас помилок ABCError, який поширюється на клас NSError, і використовуйте його для перевірених винятків, а не NSExceptions. Піднімайте NSExceptions, коли виникають помилки програміста (несподівана ситуація, наприклад, проблема формату чисел).
чатхурам

15

Я думаю, щоб бути консистентом, приємніше використовувати @throw з власним класом, який розширює NSException. Потім ви використовуєте ті самі позначення, щоб спробувати зловити нарешті:

@try {
.....
}
@catch{
...
}
@finally{
...
}

Apple пояснює тут, як кидати та обробляти винятки: Ловлячи винятки, кидаючи винятки


У мене все ще
трапляється

14

Оскільки ObjC 2.0, винятки Objective-C більше не є пакувачем для setjmp () longjmp () C і сумісні з винятком C ++, @try "безкоштовний", але кидання та вилучення винятків набагато дорожче.

У будь-якому разі, твердження (використовуючи NSAssert та NSCAssert макросімейство) кидають NSException, і це здорово використовувати їх як стан Ries.


Добре знати! У нас є стороння бібліотека, яку ми не хочемо змінювати, яка видає винятки навіть для найменших помилок. Нам потрібно ловити їх на одному місці в додатку, і це просто змушує нас скупитися, але це змушує мене почуватися трохи краще.
Юрій Бриганс

8

Використовуйте NSError для повідомлення про помилки, а не винятки.

Короткі моменти щодо NSError:

  • NSError дозволяє кодам помилок (цілих чисел) стилю C чітко ідентифікувати першопричину і, сподіваємось, дозволяти оброблювачу помилок долати помилку. Ви можете легко зафіксувати коди помилок з бібліотек С, як-от SQLite, в екземплярах NSError.

  • NSError також має перевагу бути об’єктом і пропонує спосіб детальніше описати помилку зі своїм словником userInfo.

  • Але найкраще, що NSError НЕ МОЖЕТ бути кинутим, тому він заохочує більш ініціативний підхід до обробки помилок, на відміну від інших мов, які просто кидають гарячу картоплю далі та збільшують стек виклику, після чого про неї можна повідомити лише користувачеві та не обробляються жодним значущим чином (не якщо ви вірите в дотримання найбільшої довідки OOP, що приховує інформацію).

Посилання: Довідник


Цей коментар не відповідає на запитання. Можливо, ОП просто хоче збити додаток, щоб перевірити, чи працює система звітів про аварійну ситуацію, як очікувалося.
Саймон

7

Ось як я дізнався про це з "Керівництва з великого рандового ранчо (4-е видання)":

@throw [NSException exceptionWithName:@"Something is not right exception"
                               reason:@"Can't perform this operation because of this or that"
                             userInfo:nil];

Гаразд, але це не дуже розповідає про це userInfo:nil. :)
Cœur

6

Ви можете використовувати два способи підвищення винятку в блоці спробу вилову

@throw[NSException exceptionWithName];

або другий спосіб

NSException e;
[e raise];

3

Я вважаю, що ви ніколи не повинні використовувати Винятки для контролю нормального потоку програми. Але винятки слід викидати, коли якесь значення не відповідає бажаному значенню.

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

Рис


0

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


0

Приклад коду для випадку: @throw ([виняток NSExceptionWithName: ...

- (void)parseError:(NSError *)error
       completionBlock:(void (^)(NSString *error))completionBlock {


    NSString *resultString = [NSString new];

    @try {

    NSData *errorData = [NSData dataWithData:error.userInfo[@"SomeKeyForData"]];

    if(!errorData.bytes) {

        @throw([NSException exceptionWithName:@"<Set Yours exc. name: > Test Exc" reason:@"<Describe reason: > Doesn't contain data" userInfo:nil]);
    }


    NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
                                                                 options:NSJSONReadingAllowFragments
                                                                   error:&error];

    resultString = dictFromData[@"someKey"];
    ...


} @catch (NSException *exception) {

      NSLog( @"Caught Exception Name: %@", exception.name);
      NSLog( @"Caught Exception Reason: %@", exception.reason );

    resultString = exception.reason;

} @finally {

    completionBlock(resultString);
}

}

Використання:

[self parseError:error completionBlock:^(NSString *error) {
            NSLog(@"%@", error);
        }];

Ще один просунутий варіант використання:

- (void)parseError:(NSError *)error completionBlock:(void (^)(NSString *error))completionBlock {

NSString *resultString = [NSString new];

NSException* customNilException = [NSException exceptionWithName:@"NilException"
                                                          reason:@"object is nil"
                                                        userInfo:nil];

NSException* customNotNumberException = [NSException exceptionWithName:@"NotNumberException"
                                                                reason:@"object is not a NSNumber"
                                                              userInfo:nil];

@try {

    NSData *errorData = [NSData dataWithData:error.userInfo[@"SomeKeyForData"]];

    if(!errorData.bytes) {

        @throw([NSException exceptionWithName:@"<Set Yours exc. name: > Test Exc" reason:@"<Describe reason: > Doesn't contain data" userInfo:nil]);
    }


    NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
                                                                 options:NSJSONReadingAllowFragments
                                                                   error:&error];

    NSArray * array = dictFromData[@"someArrayKey"];

    for (NSInteger i=0; i < array.count; i++) {

        id resultString = array[i];

        if (![resultString isKindOfClass:NSNumber.class]) {

            [customNotNumberException raise]; // <====== HERE is just the same as: @throw customNotNumberException;

            break;

        } else if (!resultString){

            @throw customNilException;        // <======

            break;
        }

    }

} @catch (SomeCustomException * sce) {
    // most specific type
    // handle exception ce
    //...
} @catch (CustomException * ce) {
    // most specific type
    // handle exception ce
    //...
} @catch (NSException *exception) {
    // less specific type

    // do whatever recovery is necessary at his level
    //...
    // rethrow the exception so it's handled at a higher level

    @throw (SomeCustomException * customException);

} @finally {
    // perform tasks necessary whether exception occurred or not

}

}


-7

Немає ніяких причин не використовувати винятки, як правило, в об'єктивній C, навіть щоб позначити винятки з бізнес-правил. Apple може сказати, що використовує NSError, який піклується. Obj C існував довгий час, і свого часу ВСІ документи C ++ говорили те саме. Причина, з якої не важливо, наскільки дорого викидання і вилов винятку, полягає в тому, що термін експлуатації винятку надзвичайно короткий і ... його ВИКОНАННЯ до нормального потоку. Я ніколи в житті не чув, щоб хтось говорив, що людину цього винятку потрібно було довго кидати і ловити.

Крім того, є люди, які вважають, що сам об'єктив C занадто дорогий і замість цього кодує C або C ++. Так що сказати, що завжди використовувати NSError є неінформованою та параноїчною.

Але на питання цієї теми ще не відповіли, який найкращий спосіб кинути виняток. Способи повернення NSError очевидні.

Так це: [підвищення NSException: ... @throw [[NSException alloc] initWithName .... або @throw [[MyCustomException ...?

Я використовую тут перевірене / неперевірене правило трохи інакше, ніж вище.

Справжня різниця між (використовуючи тут метафору java) відміченою / неперевіреною є важливою -> чи можна відновитись після винятку. І під відновленням я маю на увазі не просто НЕ аварії.

Тому я використовую власні класи винятків з @throw для відшкодування винятків, тому що, швидше за все, у мене буде метод додатків, який шукає певні типи відмов у кількох блоках @catch. Наприклад, якщо в моєму додатку є банкомат, у мене буде блок @catch для "WithdrawalRequestExceedsBalanceException".

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

У всякому разі, що я роблю, але якщо є кращий, подібний виразний спосіб, я хотів би також знати. У своєму власному коді, оскільки я давно перестала кодувати C a hella, я ніколи не повертаю NSError, навіть якщо мене передають API.


4
Я рекомендую спробувати запрограмувати сервер за винятками як частину звичайного потоку випадків помилок перед тим, як робити такі узагальнюючі заяви, як "немає причин не використовувати винятки, як правило, в об'єкті C". Вірите чи ні, є причини писати додатки з високою продуктивністю (або принаймні частини додатків) в ObjC, а викидання винятків, як правило, серйозно утрудняє продуктивність.
jbenet

6
Дійсно є дуже вагомі причини, чому не використовувати винятки в какао. Дивіться відповідь Білла Бамгарнера тут для додаткової інформації: stackoverflow.com/questions/3378696/iphone-try-end-try/… . Він знає, про що йде мова (підказка: перевірити свого роботодавця). Винятки з какао трактуються як непоправні помилки і можуть залишити систему в нестабільному стані. NSError - це спосіб вирішити загальні помилки.
Бред Ларсон

Винятки є винятковими . Невдачі в бізнес-правилах, безумовно, не підлягають. "Пошук і розробка далеко не важких кодів може призвести до пристойного виграшу." MSDN через codinghorror.com/blog/2004/10/…
Джонатан Ватмуф

3
Винятки не можна скидати з блоків. Винятки, викинуті в середовищі ARC, можуть призвести до протікання вашої програми. Тим самим потік.
Moszi

"Не важливо, наскільки дорого викидання і вилов винятку" я пишу емулятор, коли продуктивність є критичною. Я не можу кинути цілу купу дорогих винятків.
НіхтоНада
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.