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

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

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


person Nic Hubbard    schedule 11.01.2011    source источник


Ответы (9)


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

Также есть хороший простой учебник по Какао - моя подруга.

person Alex    schedule 11.01.2011
comment
это самый смешной пример, когда-либо - person ming yeow; 18.06.2011
comment
это довольно крутой ответ, хотя есть некоторые проблемы в ARC и преобразовании id в BOOL. Любая небольшая вариация, совместимая с ARC, будет принята с благодарностью. - person NSTJ; 05.12.2012
comment
@TomJowett Я был бы очень зол, если бы мы в конечном итоге не смогли покончить с голодом в мире просто потому, что Apple подтолкнула нас к переходу в новый мир ARC. - person Manav; 19.12.2012
comment
тип возврата может быть BOOL. Верните NO в случае ошибки и вместо проверки возвращаемого значения просто проверьте error. Если nil продолжайте, если != nil обработайте это. - person Gabriele Petronella; 30.12.2012
comment
-1: Вам действительно нужно включить код, который проверяет, что **error не равно нулю. В противном случае программа выдаст ошибку, которая является совершенно недружественной и не дает понять, что происходит. - person FreeAsInBeer; 07.03.2013
comment
Я думаю, у Алекса есть рабочий код, который позже заставит его смеяться над собственными комментариями ... знайте это чувство - person CularBytes; 19.07.2015

Я хотел бы добавить еще несколько предложений, основанных на моей последней реализации. Я просмотрел код от 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];
person Wolfgang Schreurs    schedule 29.12.2012
comment
Отличный ответ! Но почему бы не поместить локализованное описание в словарь пользовательской информации, которому оно принадлежит? [NSError errorWithDomain: FSMyAppErrorDomain code: FSProfileParsingFailedError userInfo: @ {NSLocalizedDescriptionKey: FS_ERROR_LOCALIZED_DESCRIPTION (error.code)}]; - person Richard Venable; 02.08.2013
comment
Есть ли какое-то конкретное место, куда я должен поместить строковый файл? Из FS_ERROR_LOCALIZED_DESCRIPTION () я получаю только номер (код ошибки). - person huggie; 10.12.2013
comment
@huggie: не совсем понимаю, что ты имеешь в виду. Я обычно помещаю эти макросы, которые использую во всем приложении, в файл с именем Constants+Macros.h и импортирую этот файл в заголовок префикса (файл .pch), чтобы он был доступен везде. Если вы имеете в виду, что используете только один из двух макросов, это может сработать. Возможно, преобразование из int в NSString действительно не нужно, хотя я это не тестировал. - person Wolfgang Schreurs; 10.12.2013
comment
@huggie: ой, думаю, теперь я тебя понимаю. Строки должны быть в локализуемом файле (.strings файл), так как именно там будет искать макрос Apple. Прочтите об использовании NSLocalizedStringFromTable здесь: developer.apple. com / library / mac / documentation / cocoa / conceptual / - person Wolfgang Schreurs; 10.12.2013
comment
Я спросил, потому что по какой-то причине вместо значимых фраз у меня появляется код 1001, 1002. Не удалось выйти из системы. - person huggie; 11.12.2013
comment
@huggie: Да, я использовал локализованные строковые таблицы. Код в макросе FS_ERROR_LOCALIZED_DESCRIPTION проверяет локализуемую строку в файле с именем FSError.strings. Возможно, вы захотите ознакомиться с руководством Apple по локализации .strings файлов, если оно вам незнакомо. - person Wolfgang Schreurs; 11.12.2013
comment
В случае, если вы охватываете все части вашего приложения для включения сообщения об ошибке, это может быть замечательно, но если вы разработчик фреймворка, который должен экспортировать вашу фреймворк другому разработчику, вы должны отправить его с файлом .strings, который, кажется, быть ненормальным, неэффективным. Если вы находитесь в рамках фреймворка, вам нужно определить сообщения об ошибках где-нибудь, например, заголовок префикса и т. Д. - person KoreanXcodeWorker; 21.02.2019

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

Цель-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"])
person AlBeebe    schedule 09.01.2014

См. Следующее руководство

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

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

person Tirth    schedule 11.01.2011

Я попытаюсь обобщить отличный ответ Алекса и точку зрения 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.
person Gabriele Petronella    schedule 30.12.2012
comment
@Gabriela: Apple заявляет, что при использовании косвенных переменных для возврата ошибок сам метод всегда должен иметь какое-то возвращаемое значение в случае успеха или неудачи. Apple настоятельно рекомендует разработчикам сначала проверить возвращаемое значение и только, если возвращаемое значение каким-то образом недействительно, проверьте наличие ошибок. См. Следующую страницу: developer.apple. com / library / mac / # documentation / Cocoa / Conceptual / - person Wolfgang Schreurs; 31.12.2012

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

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

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");
    }
}];
person Senseful    schedule 08.07.2013

extension NSError {
    static func defaultError() -> NSError {
        return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
    }
}

который я могу использовать NSError.defaultError() всякий раз, когда у меня нет действительного объекта ошибки.

let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.
person Hemang    schedule 02.04.2020

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

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