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

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

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

Ответ 1

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

Пример:

- (id) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
    // begin feeding the world children...
    // it 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 children...return nil..sniffle...sniffle
        return nil;
    }
    // wohoo! We fed the world 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. Это действительно хорошо.

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

Ответ 2

Я хотел бы добавить еще несколько предложений, основанных на моей самой последней реализации. Я просмотрел какой-то код от 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];

Ответ 3

Отличный ответ Алекс. Одной из потенциальных проблем является 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 children...return nil..sniffle...sniffle
return nil;
...

Ответ 4

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"])

Ответ 5

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

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

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

Ответ 6

Я попытаюсь обобщить замечательный ответ Alex и точку jlmendezbonini, добавив модификацию, которая сделает все ARC совместимым (пока это не так, поскольку ARC будет жаловаться, так как вы должны вернуть id, что означает "любой объект", но BOOL не является типом объекта).

- (BOOL) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
    // begin feeding the world children...
    // it 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 children...return nil..sniffle...sniffle
        return NO;
    }
    // wohoo! We fed the world 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.

Ответ 7

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

Скажем, мы определили следующие коды ошибок:

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

Ответ 8

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

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