Я працюю над тим, щоб зафіксувати помилки в моєму додатку NSError. Я трохи заплутаний у тому, як ним користуватися та як заповнити його.
Чи міг би хтось навести приклад того, як я заповнюю потім використання NSError?
Я працюю над тим, щоб зафіксувати помилки в моєму додатку NSError. Я трохи заплутаний у тому, як ним користуватися та як заповнити його.
Чи міг би хтось навести приклад того, як я заповнюю потім використання NSError?
Відповіді:
Що ж, як правило, я маю свої методи, які могли б помилки під час виконання приймати посилання на 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 . Це дійсно добре.
Також є приємний, простий підручник на тему « Какао - моя подруга» .
idна a BOOL. Будемо дуже вдячні за будь-які незначні зміни, сумісні з ARC.
BOOL. Повернення NOу разі помилки, а замість того, щоб перевіряти значення повернення, просто перевірити error. Якщо nilйти вперед, якщо != nilвпоратися.
**errorце не нуль. Інакше програма видасть помилку, яка є абсолютно недружньою і не дає зрозуміти, що відбувається.
Я хотів би додати ще кілька пропозицій на основі моєї останньої реалізації. Я переглянув якийсь код від 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];
Constants+Macros.hта імпортую цей файл у заголовок префікса ( .pchфайл), щоб він був доступний скрізь. Якщо ви хочете сказати, що ви використовуєте лише 1 з 2 макросів, це може працювати. Можливо, перетворення з intна NSStringсправді не потрібне, хоча я цього не перевіряв.
.stringsфайлі), оскільки саме там буде виглядати макрос Apple. Про використання NSLocalizedStringFromTableтут читайте: developer.apple.com/library/mac/documentation/cocoa/conceptual/…
FS_ERROR_LOCALIZED_DESCRIPTIONперевіряє локалізаційний рядок у файлі, який називається FSError.strings. Ви можете перевірити посібник з локалізації .stringsфайлів Apple, якщо вони вам не чужі.
Чудова відповідь Алекс. Одне потенційне питання - відпуск 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;
...
Будь ласка, зверніться до наступного підручника
Я сподіваюся, що це стане в нагоді для вас, але попередньо вам доведеться ознайомитись з документацією на NSError
Це дуже цікаве посилання, яке я знайшов нещодавно ErrorHandling
Я спробую підсумувати чудову відповідь Алекса та точку 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.
Ще одна модель дизайну, яку я бачив, передбачає використання блоків, що особливо корисно, коли метод запускається асинхронно.
Скажімо, у нас визначені такі коди помилок:
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");
}
}];
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.