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

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

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

Ответы [ 8 ]

468 голосов
/ 11 января 2011

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

Есть также хороший, простой урок по Cocoa Is My Girlfriend .

54 голосов
/ 30 декабря 2012

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

Отличный ответ, Алекс.Одна потенциальная проблема - разыменование 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;
...
27 голосов
/ 09 января 2014

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"])
9 голосов
/ 11 января 2011

Пожалуйста, ознакомьтесь с учебником

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

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

3 голосов
/ 08 июля 2013

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

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

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");
    }
}];
3 голосов
/ 30 декабря 2012

Я постараюсь обобщить отличный ответ Алекса и точку зрения 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.
0 голосов
/ 14 февраля 2014

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

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