3处理异常

作者: 蜗牛你慢慢来 | 来源:发表于2019-07-26 15:06 被阅读0次

    Objective-C程序可用的异常处理机制是处理异常情况的有效方法。它们将这些条件的检测和处理分离开来,并自动将异常从监测点传播到处理点。因此,你的代码可以更干净,更容易正确地编写和维护。

    使用编译器指令处理异常

    编译器对异常的支持基于四个编译器指令:

    • @try ---- 定义一个异常处理域的代码块:可能引发异常的代码。
    • @catch() ---- 定义一个块,该块包含处理@try块中引发的异常的代码。@catch的参数是在本地引发的异常对象;这通常是一个NSException对象,但可以是其他类型的对象,如NSString对象。
    • @finally ---- 定义随后执行的相关代码块,无论是否抛出异常。
    • @throw ---- 抛出异常:此指令的行为与NSException的raise方法几乎相同。通常抛出NSException对象,但不限于这些对象。有关@throw的更多信息,请参阅抛出异常

    重要提示:尽管你可以抛出和捕获NSException对象以外的对象,但Cocoa框架本身可能在某些情况下只捕获NSException对象。因此,如果抛出其他类型的对象,则该异常的Cocoa处理程序可能不会运行,结果未定义。(相反,你抛出的非NSException对象可能会被一些Cocoa处理程序捕获。)基于这些原因,建议你只抛出NSException对象,同时准备捕获所有类型的异常对象。

    @try, @catch和@finally指令构成控制结构。@try中大括号之间的代码部分是异常处理域;@catch块中的代码是本地异常处理程序;@finally块是常见的“内务管理”部分。在图1中,程序执行的正常流程用灰色箭头标记;只有当本地异常处理域或调用序列的下一步抛出异常时,才会执行本地异常处理程序中的代码。异常的抛出会导致程序控制跳转到本地异常处理程序的第一个可执行行。处理异常后,程序“跌到”@finally块;如果未引发异常,则程序从@try块调到@finaly块。

    使用编译器指令的异常处理流程

    处理异常的位置和方式取决于引发异常的上下文(尽管大多数程序中的大多数异常在到达由共享NSApplication或者UIApplication对象安装的顶级处理程序之前都不会被捕获)。通常,在异常处理程序的域中抛出(或引发)异常对象。尽管你可以直接在本地异常处理域中引发异常,但异常更可能是从域调用的方法间接引发的(通过@throw或raise)。无论异常在调用序列中的深度如何,执行都会跳到本地异常处理程序(假设没有中间的异常处理程序,如嵌套异常处理程序中所述)。这样,在低级别引发的异常可以在高级别捕获。

    代码段1说明了如何使用@try, @catch和@finally编译器指令。在本例中,@catch块通过将受影响的属性设置为nil来处理由于setValue:forkeyPath:在调用序列中较低位置抛出的任何异常。无论是否抛出异常,@finally块中的消息都将被发送。

    - (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];
              }
            }
        }
    

    代码段1 使用编译器指令处理一个异常

    处理异常的一种方法是将异常“升级”为错误消息,这些错误消息要么通知用户,要么请求用户干预。你可以将异常转换为NSError对象,然后在警告框中将错误对象中的信息呈现给用户。在OS X中,你还可以将此对象移交给应用程序工具包的错误处理机制,以便向用户显示。还可以在包含错误参数的方法中间接返回他们。代码段2显示了后者的一个示例,Automator操作的实现,runWithInput:fromAction:error: (在本例中,错误参数是指向NSDictionary对象而不是NSError对象的指针)。

     - (id)runWithInput:(id)input fromAction:(AMAction *)anAction error:(NSDictionary **)errorInf
    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 ....
        }
    

    代码段2 将异常转换成错误

    注意:有关应用程序工具包错误处理机制的更多信息,请参阅《错误处理编程指南》
    要了解有关自动机操作的更多信息,请参阅《自动机编程指南》

    你可以有一个@catch错误处理块序列。每个块处理不同类型的异常对象。你应该将@catch块的序列从最特定的异常对象类型排序到最不特定的异常对象类型(最不特定的类型是id),如代码段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
        ...
        }
    

    代码段3 异常处理序列

    注意:如果跳转需要跨越@try块,则不能使用setjmp和longjmp函数。由于程序调用的代码中可能包含异常处理域,请避免在应用程序中使用setjmp和longjmp。但是,你可以使用goto或者return退出异常处理域。

    异常处理和内存管理

    使用Objective-C的异常处理指令可能会是内存管理复杂化,但有一点常识,你可以表面这些陷阱。为了了解如何进行,让我们从简单的例子开始:如下的方法,为了提高效率,创建一个对象,使用它,然后显式地释放它:

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

    这里的问题很明显:如果doSomethingElse:方法抛出异常,则会出现内存泄露。但解决方案同样显而易见:将release移动到@finally块:

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

    这种使用@try...@finally:来释放异常中涉及到的对象的模式也适用于其他资源。如果你有malloc的内存块或打开的文件描述符,@finally块是一个释放它们的好地方;它也是解锁你所获得的任何锁的理想地方。

    另一个更微妙的内存管理问题是在存在内部自动释放池时过度释放异常对象。几乎所有的NSException对象(和其他类型的异常对象)都是自动释放的,它们被分配给最近的(作用域内)自动释放池。当释放该池时,异常将被销毁。一个池可以直接被释放,也可以由于一个自动释放池被弹出(即释放)到堆栈的下一步(从而进一步超出作用域)。考虑这种方法:

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

    此代码似乎是健全的;如果doSomethingElse:导致抛出异常,当堆栈上较低(或外部)的自动释放池弹出时,将释放本地自动释放池。但是有一个潜在的问题。正如Throwing Exceptions(下一节内容)所揭示的,作为早期副作用,重新抛出的异常会导致其关联的@finally块被执行。如果在@finally块中释放了外部自动释放池,则本地池会在传递异常之前被释放,从而导致“僵尸”异常。

    有几种方法可以解决这个问题。最简单的方法是避免释放@finally块中的自动释放池。相反,让一个更深的池弹出来处理释放包含异常对象的池。但是,如果在异常向上传播到栈时没有弹出更深的池,则堆栈上的池将泄露内存;这些池中的所有对象将保持未释放状态,直到线程被破坏。

    另一种方法是捕获任何抛出的异常,保留它,然后再次抛出它。然后,在@finally块中,释放自动释放池并自动释放异常对象。代码段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];
        }
    

    代码段4 释放包含异常对象的自动释放池

    这样做会保留内部自动释放过程中引发的异常—即异常在离开doSomethingElse:的过程中被放入的池—并确保它在下一个自动释放池中自动释放到作用域中(或者,从另一个角度来看,堆栈上它下面的自动释放池)。为了使事情正常工作,内部自动释放池的释放必须在保留的异常对象自动释放之前发生。

    翻译源:

    相关文章

      网友评论

        本文标题:3处理异常

        本文链接:https://www.haomeiwen.com/subject/pkxdrctx.html