Использование обработки исключений в сравнении с NSError в приложениях Cocoa
Эй, все. Я читал рекомендации Apple о том, когда/где/как использовать NSError по сравнению с @try/@catch/@. По сути, у меня сложилось впечатление, что Apple думает, что лучше избегать использования языковых конструкций обработки исключений, кроме как механизма остановки выполнения программы в неожиданных ситуациях с ошибками (может быть, кто-то мог бы привести пример такой ситуации?)
Я прихожу из Java, где исключения - это путь, когда нужно обрабатывать ошибки. По общему признанию, я все еще в Java-пространстве, но я медленно приступаю к работе со всем, что может предложить NSError.
Одна вещь, которую я повесил, - это задача очистки памяти при возникновении ошибки. Во многих ситуациях (например, с использованием библиотек C, С++, CoreFoundation и т.д.) У вас много очистки памяти, которая должна быть выполнена до выхода из функции из-за ошибки.
Вот пример, который я приготовил, который точно отражает ситуации, с которыми я сталкивался. Используя некоторые воображаемые структуры данных, функция открывает дескриптор файла и создает объект MyFileRefInfo, который содержит информацию о том, что делать с файлом. Некоторые вещи делаются с файлом до того, как дескриптор файла закрыт, и память для освобожденной структуры. Используя предложения Apple, у меня есть этот метод:
- (BOOL)doSomeThingsWithFile:(NSURL *)filePath error:(NSError **)error
{
MyFileReference inFile; // Lets say this is a CF struct that opens a file reference
MyFileRefInfo *fileInfo = new MyFileRefInfo(...some init parameters...);
OSStatus err = OpenFileReference((CFURLRef)filePath ,&inFile);
if(err != NoErr)
{
*error = [NSError errorWithDomain:@"myDomain" code:99 userInfo:nil];
delete fileInfo;
return NO;
}
err = DoSomeStuffWithTheFileAndInfo(inFile,fileInfo);
if(err != NoErr)
{
*error = [NSError errorWithDomain:@"myDomain" code:100 userInfo:nil];
CloseFileHandle(inFile); // if we don't do this bad things happen
delete fileInfo;
return NO;
}
err = DoSomeOtherStuffWithTheFile(inFile,fileInfo);
if(err != NoErr)
{
*error = [NSError errorWithDomain:@"myDomain" code:101 userInfo:nil];
CloseFileHandle(inFile); // if we don't do this bad things happen
delete fileInfo;
return NO;
}
CloseFileHandle(inFile);
delete fileInfo;
return YES;
}
Теперь... моя Java-логика говорит мне, что было бы лучше установить это как структуру try/catch/finally и поместить все вызовы, чтобы закрыть дескриптор файла и освободить память в блоке finally.
Так вот..
...
@try
{
OSStatus err = OpenFileReference((CFURLRef)filePath ,&inFile);
if(err != NoErr)
{
... throw some exception complete with error code and description ...
}
err = DoSomeStuffWithTheFileAndInfo(inFile,fileInfo);
if(err != NoErr)
{
... throw some exception ...
}
... etc ...
}
@catch(MyException *ex)
{
*error = [NSError errorWithDomain:@"myDomain" code:[ex errorCode] userInfo:nil];
return NO;
}
@finally
{
CloseFileHandle(inFile); // if we don't do this bad things happen
delete fileInfo;
}
return YES;
Неужели я сумасшедший, думая, что это гораздо более элегантное решение с меньшим избыточным кодом?
Я что-то пропустил?
Ответы
Ответ 1
Даниэль ответил правильно, но этот вопрос заслуживает более грубого ответа.
Выбросьте исключение только в случае возникновения ошибки, не подлежащей восстановлению.
Используйте NSError при общении с условиями ошибки, которые могут быть восстановлены.
Любое исключение, которое создается через фрейм в каркасах Apple, может привести к поведению undefined.
В центре dev есть документ с темой программирования исключений.
Ответ 2
По сути, у меня сложилось впечатление, что Apple думает, что лучше избегать использования языковых конструкций обработки исключений, за исключением механизма остановки выполнения программы в непредвиденных ситуациях (возможно, кто-то мог бы привести пример такой ситуации?)
Это не совсем мое впечатление. Я думал, что Apple предлагает использовать исключения для действительно исключительных условий и NSError для ожидаемых сбоев. Поскольку вы пришли из Java, я думаю, что NSError → java.lang.Exception и Obj-C Exceptions → java.lang.RuntimeException. Используйте исключение Obj-C, когда программист сделал что-то неправильно (например, неправильно использовал API) и использовал NSError, если ожидаемый сбой произошел (например, удаленный сервер не найден).
Конечно, это только моя интерпретация позиции Apple. Я, с другой стороны, люблю исключения!
Ответ 3
Исключения в Objective-C исторически были "тяжелыми", с затратами на производительность, чтобы войти в блок try, стоимость броска, затраты на использование, наконец, и т.д. В результате Cocoa разработчики обычно избегали исключений за пределами "нет, небо падает" в ситуациях, когда файл отсутствует, используйте NSError, но если нет файловой системы и отрицательного количества свободной памяти, это исключение.
Это исторический взгляд. Но если вы создаете 64-битное приложение на 10,5 или новее, архитектура исключений была переписана как "нулевая стоимость", что может означать, что историческое представление больше не актуально. Как и в любом случае, это сводится к различным факторам: если работа в одном случае более естественна для вас и позволит вам закончить быстрее, и если у вас не возникнут проблемы с производительностью, и если вы немного несовместимый с "традиционным" Objective-C кодом, не беспокоит вас... тогда нет причин не использовать исключения.
Ответ 4
В соответствии с More iPhone 3 Development
Дэйвом Марком и Джеффом ЛеМаршем исключения используются только для действительно исключительных ситуаций и обычно указывают на проблему внутри вашего кода. Вы никогда не должны использовать исключения для отчета о запуске, из-за ошибки. Исключения используются с гораздо меньшей частотой в Objective-C, чем во многих других языках, таких как Java и С++.
Вы используете исключение, когда вам нужно поймать ошибку в коде. Вы используете ошибку, когда пользователю может понадобиться устранить проблему.
Вот пример использования исключения:
Мы пишем суперкласс, и мы хотим убедиться, что его подклассы реализуют данный метод. Objective-C не имеет абстрактных классов, и ему не хватает механизма, чтобы заставить подкласс реализовать данный метод. Однако мы можем использовать исключение, чтобы немедленно сообщить нам, что мы забыли реализовать этот метод в подклассе. Вместо непредсказуемого поведения мы получим исключение из среды выполнения. Мы можем легко отладить его, потому что наше исключение точно скажет нам, что мы сделали неправильно:
NSException *ex = [NSException exceptionWithName:@"Abstract Method Not Overridden" reason:NSLocalizedString(@"You MUST override the save method", @"You MUST override the save method") userInfo:nil];
[ex raise];
Поскольку проблема - ошибка программиста, а не проблема, которую пользователь может исправить, мы используем исключение.