Який найкращий спосіб викинути виняток у target-c / какао?
Який найкращий спосіб викинути виняток у target-c / какао?
Відповіді:
Я використовую [NSException raise:format:]
наступне:
[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
Тут слово обережності. У Objective-C, на відміну від багатьох подібних мов, вам, як правило, слід намагатися уникати використання виключень для поширених ситуацій помилок, які можуть виникати в звичайній роботі.
Документація Apple для Obj-C 2.0 зазначає наступне: "Важливо: Винятки є ресурсомісткими в" Objective-C ". Ви не повинні використовувати винятки для загального контролю потоку або просто для позначення помилок (таких як файл недоступний)"
Концептуальна документація по обробці винятків Apple пояснює те саме, але більше слів: "Важливо. Ви повинні зарезервувати використання виключень для програмування або несподіваних помилок виконання, таких як позабіржовий доступ до колекції, спроби мутувати незмінні об'єкти, надсилаючи недійсне повідомлення і втрачаєш з'єднання з віконним сервером. Ти зазвичай берешся за подібні помилки, за винятком випадків, коли програма створюється, а не під час виконання. [.....] Замість винятків, об'єкти помилок (NSError) та Механізм доставки помилок какао є рекомендованим способом повідомлення очікуваних помилок у програмах какао ".
Причини цього частково полягають у дотриманні ідіом програмування в Objective-C (використання повернених значень у простих випадках та посилальних параметрів (найчастіше клас NSError) у складніших випадках), частково те, що кидання та вилучення винятків набагато дорожче та нарешті (і найважливіше перебирає), що винятки Objective-C - це тонка обгортка навколо функцій setjmp () і longjmp () C, яка по суті псує ваше обережне управління пам’яттю, див. це пояснення .
@throw([NSException exceptionWith…])
Xcode розпізнає @throw
оператори як точки виходу з функції, як return
оператори. Використання @throw
синтаксису дозволяє уникнути помилкових попереджень " Керування може дійти до кінця недійсної функції " [NSException raise:…]
.
Також @throw
можна використовувати для метання об'єктів, які не належать до класу NSException.
Що стосується [NSException raise:format:]
. Для тих, хто походить з фону Java, ви згадаєте, що Java розрізняє виняток і RuntimeException. Виняток - це перевірений виняток, а RuntimeException не встановлено. Зокрема, Java пропонує використовувати перевірені винятки для "нормальних умов помилок" та неперевірені винятки для "помилок виконання, викликаних помилкою програміста". Схоже, винятки Objective-C повинні використовуватися в тих самих місцях, де ви б використовували неперевірений виняток, а значення повернення коду помилки або значення NSError є кращими в тих місцях, де ви б використовували перевірене виключення.
Я думаю, щоб бути консистентом, приємніше використовувати @throw з власним класом, який розширює NSException. Потім ви використовуєте ті самі позначення, щоб спробувати зловити нарешті:
@try {
.....
}
@catch{
...
}
@finally{
...
}
Apple пояснює тут, як кидати та обробляти винятки: Ловлячи винятки, кидаючи винятки
Оскільки ObjC 2.0, винятки Objective-C більше не є пакувачем для setjmp () longjmp () C і сумісні з винятком C ++, @try "безкоштовний", але кидання та вилучення винятків набагато дорожче.
У будь-якому разі, твердження (використовуючи NSAssert та NSCAssert макросімейство) кидають NSException, і це здорово використовувати їх як стан Ries.
Використовуйте NSError для повідомлення про помилки, а не винятки.
Короткі моменти щодо NSError:
NSError дозволяє кодам помилок (цілих чисел) стилю C чітко ідентифікувати першопричину і, сподіваємось, дозволяти оброблювачу помилок долати помилку. Ви можете легко зафіксувати коди помилок з бібліотек С, як-от SQLite, в екземплярах NSError.
NSError також має перевагу бути об’єктом і пропонує спосіб детальніше описати помилку зі своїм словником userInfo.
Але найкраще, що NSError НЕ МОЖЕТ бути кинутим, тому він заохочує більш ініціативний підхід до обробки помилок, на відміну від інших мов, які просто кидають гарячу картоплю далі та збільшують стек виклику, після чого про неї можна повідомити лише користувачеві та не обробляються жодним значущим чином (не якщо ви вірите в дотримання найбільшої довідки OOP, що приховує інформацію).
Посилання: Довідник
Ось як я дізнався про це з "Керівництва з великого рандового ранчо (4-е видання)":
@throw [NSException exceptionWithName:@"Something is not right exception"
reason:@"Can't perform this operation because of this or that"
userInfo:nil];
userInfo:nil
. :)
Я вважаю, що ви ніколи не повинні використовувати Винятки для контролю нормального потоку програми. Але винятки слід викидати, коли якесь значення не відповідає бажаному значенню.
Наприклад, якщо якась функція приймає значення, і це значення ніколи не допускається до нуля, тоді добре викинути виняток, а не намагатися зробити щось "розумне" ...
Рис
Виключення слід викидати лише в тому випадку, якщо ви опинилися в ситуації, яка вказує на помилку програмування, і хочете зупинити роботу програми. Тому найкращий спосіб викинути винятки - це використання макросів NSAssert та NSParameterAssert та переконайтесь, що NS_BLOCK_ASSERTIONS не визначено.
Приклад коду для випадку: @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
}
}
Немає ніяких причин не використовувати винятки, як правило, в об'єктивній 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.
@throw([NSException exceptionWith…])
підходу, оскільки більш стислому.