Обработка исключений

Механизмы обработки исключений, доступные программам Objective C, являются эффективными способами иметь дело с исключительными условиями. Они разъединяют обнаружение и обработку этих условий и автоматизируют распространение исключения из точки обнаружения на грани обработки. В результате Ваш код может быть намного более чистым, проще записать правильно, и проще поддержать.

Обработка исключений Используя директивы компилятора

Поддержка компилятора исключений основывается на четырех директивах компилятора:

@try, @catch, и @finally директивы составляют управляющую структуру. Раздел кода между фигурными скобками в @try домен обработки исключений; код в a @catch блок является локальным обработчиком исключений; @finally блок кода является общим разделом «обслуживания». На рисунке 1 нормальный поток реализации программы отмечен серой стрелкой; код в локальном обработчике исключений выполняется, только если исключение выдается — или локальным доменом обработки исключений или одним далее вниз последовательность вызова. Бросок (или повышение) исключения заставляет программное управление переходить к первой исполнимой строке локального обработчика исключений. После того, как исключение обрабатывается, управление «проваливается» к @finally блок; если никакое исключение не выдается, управление спрыгивает @try блокируйте к @finally блок.

  Поток рисунка 1 обработки исключений с помощью директив компилятора
Flow of exception handling using compiler directives

То, где и как исключение обрабатывается, зависит от контекста, где исключение было повышено (несмотря на то, что большинство исключений в большинстве программ идет непойманное, пока они не достигают обработчика верхнего уровня, установленного совместно используемым NSApplication или UIApplication объект). В целом объект исключения брошен (или повышен) в домене обработчика исключений. Несмотря на то, что можно выдать исключение непосредственно в локальном домене обработки исключений, исключение более вероятно выдается (через @throw или raise) косвенно от метода вызывается от домена. Независимо от того то, как глубоко в вызове упорядочивают исключение, брошено, переходы выполнения к локальному обработчику исключений (предполагающий, что нет никаких прошедших обработчиков исключений, как обсуждено во Вложенных Обработчиках исключений). Таким образом исключения, повышенные на низком уровне, могут быть пойманы на высоком уровне.

Перечисление 1 иллюстрирует, как Вы могли бы использовать @try, @catch, и @finally директивы компилятора. В этом примере, @catch блокируйте обрабатывает любое исключение, выданное ниже в вызывающей последовательности в результате setValue:forKeyPath: сообщение путем установки затронутого свойства в nil вместо этого. Сообщение в @finally блок отправляется, выдается ли исключение или нет.

Перечисление 1  , Обрабатывающее исключение с помощью директив компилятора

- (void)endSheet:(NSWindow *)sheet
{
    BOOL success = [predicateEditorView commitEditing];
    if (success == YES) {
 
        @try {
            [treeController setValue:[predicateEditorView predicate] forKeyPath:@"selection.predicate"];
        }
 
        @catch ( NSException *e ) {
            [treeController setValue:nil forKeyPath:@"selection.predicate"];
        }
 
        @finally {
            [NSApp endSheet:sheet];
        }
    }
}

Один способ обработать исключения состоит в том, чтобы «продвинуть» их сообщения об ошибках, что или сообщите пользователям или запросите их вмешательство. Можно преобразовать исключение в NSError возразите и затем представьте информацию в ошибочном объекте пользователю в предупредительной панели. В OS X Вы могли также передать этот объект к механизму обработки ошибок Набора Приложения для дисплея пользователям. Можно также возвратить их косвенно в методах, включающих параметр ошибок. Перечисление 2 показывает пример последнего в реализации действия Automator runWithInput:fromAction:error: (в этом случае параметр ошибок является указателем на NSDictionary возразите, а не NSError объект).

Перечисление 2  , Преобразовывающее исключение в ошибку

- (id)runWithInput:(id)input fromAction:(AMAction *)anAction error:(NSDictionary **)errorInfo {
 
    NSMutableArray *output = [NSMutableArray array];
    NSString *actionMessage = nil;
    NSArray *recipes = nil;
    NSArray *summaries = nil;
 
    // other code here....
 
    @try {
        if (managedObjectContext == nil) {
            actionMessage = @"accessing user recipe library";
            [self initCoreDataStack];
        }
        actionMessage = @"finding recipes";
        recipes = [self recipesMatchingSearchParameters];
        actionMessage = @"generating recipe summaries";
        summaries = [self summariesFromRecipes:recipes];
    }
    @catch (NSException *exception) {
        NSMutableDictionary *errorDict = [NSMutableDictionary dictionary];
        [errorDict setObject:[NSString stringWithFormat:@"Error %@: %@", actionMessage, [exception reason]] forKey:OSAScriptErrorMessage];
        [errorDict setObject:[NSNumber numberWithInt:errOSAGeneralError] forKey:OSAScriptErrorNumber];
        *errorInfo = errorDict;
        return input;
    }
 
    // other code here ....
}

У Вас может быть последовательность @catch блоки обработки ошибок. Каждый блок обрабатывает объект исключения другого типа. Необходимо упорядочить эту последовательность @catch блоки от самого специфичного до наименее специфичного типа объекта исключения (наименее определенный тип быть id), как показано в Перечислении 3. Это упорядочивание позволяет Вам адаптировать обработку исключений как группы.

  Последовательность перечисления 3 обработчиков исключений

@try {
    // code that throws an exception
    ...
}
@catch (CustomException *ce) { // most specific type
    // handle exception ce
    ...
}
@catch (NSException *ne) { // less specific type
    // do whatever recovery is necessary at his level
    ...
    // rethrow the exception so it's handled at a higher level
    @throw;
}
@catch (id ue) { // least specific type
    // code that handles this exception
    ...
}
@finally {
    // perform tasks necessary whether exception occurred or not
    ...
}

Обработка исключений и управление памятью

Используя директивы обработки исключений Objective C может усложнить управление памятью, но с небольшим здравым смыслом можно избежать ловушек. Чтобы видеть как, давайте начнем с простого случая: метод, ради эффективности, создающий объект, использует ее, и затем выпускает ее явно:

- (void)doSomething {
    NSMutableArray *anArray = [[NSMutableArray alloc] initWithCapacity:0];
    [self doSomethingElse:anArray];
    [anArray release];
}

Проблема здесь очевидна: Если doSomethingElse: метод выдает исключение существует утечка памяти. Но решение одинаково очевидно: Переместитесь release к a @finally блок:

- (void)doSomething {
    NSMutableArray *anArray = nil;
    array = [[NSMutableArray alloc] initWithCapacity:0];
    @try {
        [self doSomethingElse:anArray];
    }
    @finally {
        [anArray release];
    }
}

Этот образец использования @try...@finally выпускать объекты, вовлеченные в исключение, применяется к другим ресурсам также. Если Вы имеете malloc’d блоки памяти или открытых дескрипторов файлов, @finally хорошее место для освобождения тех; это - также идеальное место для разблокирования любых блокировок, которые Вы получили.

Когда там являются внутренними, другой, более тонкая проблема управления памятью сверхвыпускает объект исключения autorelease пулы. Почти все NSException объекты (и другие типы объектов исключения) создаются автовыпущенные, который присваивает их самому близкому (в объеме) пул автовыпуска. Когда тот пул выпущен, исключение уничтожается. Пул может быть или выпущен непосредственно или в результате пула автовыпуска далее вниз штабель (и таким образом далее в объеме) выталкиваемый (т.е. выпущенный). Рассмотрите этот метод:

- (void)doSomething {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSMutableArray *anArray = [[[NSMutableArray alloc] initWithCapacity:0] autorelease];
    [self doSomethingElse:anArray];
    [pool release];
}

Этот код, кажется, является звуковым; если doSomethingElse: передайте результаты в вызванной исключительной ситуации, локальный пул автовыпуска будет выпущен, когда будет вытолкано более низкое (или внешний) пул автовыпуска на штабеле. Но существует потенциальная проблема. Как объяснено в Выдаче Исключений, перевызванная исключительная ситуация вызывает свое связанное @finally блок, который будет выполняться как ранний побочный эффект. Если внешний пул автовыпуска выпущен в a @finally блок, локальный пул мог быть выпущен, прежде чем исключение поставлено, приведя к исключению «зомби».

Существует несколько способов разрешить эту проблему. Самое простое должно воздержаться от выпуска локальных пулов автовыпуска в @finally блоки. Вместо этого позвольте популярности более глубокого пула заботиться о выпуске пула, содержащего объект исключения. Однако, если никакой более глубокий пул никогда не будет вытолкан, поскольку исключение распространяет штабель, то пулы на штабеле пропустят память; все объекты в тех пулах остаются невыпущенными, пока не уничтожается поток.

Альтернативный подход должен был бы поймать любую вызванную исключительную ситуацию, сохранить ее и повторно бросить ее. Затем в @finally блок, выпустите пул автовыпуска и автовыпустите объект исключения. Перечисление 4 показывает, как это могло бы посмотреть в коде.

Перечисление 4  , Выпускающее пул автовыпуска, содержащий объект исключения

- (void)doSomething {
    id savedException = nil;
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSMutableArray *anArray = [[[NSMutableArray alloc] initWithCapacity:0] autorelease];
    @try {
        [self doSomethingElse:anArray];
    }
    @catch (NSException *theException) {
        savedException = [theException retain];
        @throw;
    }
    @finally {
        [pool release];
        [savedException autorelease];
    }
}

Выполнение этого сохраняет вызванную исключительную ситуацию через выпуск внутреннего пула автовыпуска — пул, из которого исключение было помещено в продвигающийся doSomethingElse:— и гарантирует, что это автовыпущено в следующем пуле автовыпуска, исходящем к нему в объеме (или, в другой перспективе, пуле автовыпуска ниже его на штабеле). Для вещей работать правильно, должен произойти выпуск внутреннего пула автовыпуска, прежде чем сохраненный объект исключения автовыпущен.