it-swarm.com.ru

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

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

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

223
Nic Hubbard

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

468
Alex

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

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


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];
54
Wolfgang Schreurs

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

Objective-C

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

Swift

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

Пожалуйста, обратитесь к следующему учебник

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

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

9
Tirth

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

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

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
Senseful

Я постараюсь обобщить отличный ответ Алекса и точку зрения 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
Gabriele Petronella

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

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