2011-01-11 9 views
208

Я работаю над ошибками ловушки в своем приложении, и я изучаю использование NSError. Я немного запутался в том, как его использовать и как его заполнять.Как я могу использовать NSError в своем приложении для iPhone?

Может ли кто-нибудь представить пример того, как я заполняю, а затем использовать NSError?

ответ

448

Ну, что я обычно это мои методы, которые могут быть ошибочными во время выполнения, ссылаются на указатель 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's documentation. Это действительно хорошо.

Существует также хороший, простой учебник по Cocoa Is My Girlfriend.

+32

это самый смешной пример, когда –

+0

это довольно удивительный ответ, хотя есть некоторые проблемы в АРК и заливке 'id' в виде' BOOL'. Любые незначительные вариации, совместимые с ARC, будут высоко оценены. – NSTJ

+6

@TomJowett Я был бы в самом разгаре, если бы мы не смогли положить конец голоду в мире просто потому, что Apple заставила нас перейти к новому миру ARC. – Manav

9

Пожалуйста, обратитесь следующие tutorial

я надеюсь, что это будет полезно для вас, но перед вы должны прочитать документацию NSError

Это очень интересная ссылка я нашел недавно ErrorHandling

36

Отличный ответ Alex. Одной из потенциальных проблем является NULL разыменование. Ссылка компании Apple на Creating and Returning NSError objects

... 
[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; 
... 
53

Я хотел бы добавить еще несколько предложений, основанных на моей последней реализации. Я просмотрел какой-то код от 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 макроса в файле Constants + Macros.h. Я всегда включаю этот файл в заголовок префикса (MyApp-Prefix.pch) для удобства.

Константы + Macros.h

// 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 (error.code)}]; –

+0

Есть ли какое-нибудь конкретное место, где я должен поместить файл строки? От FS_ERROR_LOCALIZED_DESCRIPTION() Я получаю только номер (код ошибки). – huggie

+0

@ huggie: не совсем уверен, что вы имеете в виду. Обычно я использую эти макросы, которые я использую во всем приложении в файле под названием «Constants + Macros.h», и импортирую этот файл в заголовок префикса (файл '.pch'), поэтому он доступен повсюду. Если вы имеете в виду, что используете только один из двух макросов, это может сработать. Возможно, преобразование из 'int' в' NSString' действительно не требуется, хотя я не тестировал это. –

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 настоятельно призывает разработчиков сначала проверить возвращаемое значение и ** только **, если возвращаемое значение как-то неверно проверяет наличие ошибок. См. Следующую страницу: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ErrorHandlingCocoa/CreateCustomizeNSError/CreateCustomizeNSError.html –

+0

Вы правы. Исправлено. –

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

И тогда, когда вы ее называете, вы не» t нужно беспокоиться о объявлении объекта 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"); 
    } 
}]; 
17

Objective-C

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

Swift 3

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

Спасибо! Его работа прекрасна. – Raja

0

Ну, это немного не вопрос, но если у вас нет возможности для NSError y НУ всегда может отобразить сообщение об ошибке Низкого уровня:

NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]); 
Смежные вопросы