Использование обработки исключений в сравнении с 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];

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