Как использовать NSError в приложении для iPhone?

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

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

8 ответов


Ну, что я обычно делаю, так это мои методы, которые могут ошибиться во время выполнения ссылки на NSError указатель. Если что-то действительно пойдет не так в этом методе, я могу заполнить NSError ссылка с данными об ошибках и возврат nil из метода.

пример:

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

затем мы можем использовать такой метод. Даже не трудитесь проверять объект ошибки, если метод не возвращает nil:

// 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. Это действительно хорошо.

существует также хороший, простой учебник по Какао-Моя Девушка.


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

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


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

FSError.h

FOUNDATION_EXPORT NSString *const FSMyAppErrorDomain;

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

FSError.м

#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.струны

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

2) Добавить макросы для преобразования целочисленных кодов в локализованные сообщения об ошибках. Я использовал 2 макроса в моих константах+макросы.H-файл. Я всегда включаю этот файл в заголовок префикса (MyApp-Prefix.pch) для удобство.

Константы+Макросы.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];

отличный ответ Алекс. Одной из потенциальных проблем является разыменование 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 *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"])

см. ниже учебник

Я надеюсь, что это будет полезно для вас, но прежде вы должны прочитать документацию 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");
    }
}];

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

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