第二十一条 理解Objective-C的错误模型

2017/8/28 posted in  第三章 接口与API设计

Objective-C语言不例外的也有“异常”(exception)机制,但是与其它语言肯定存在差异。我们要重新学习一下。

首先就是,“自动引用计数”(Automatic ReferenceCounting, ARC)在默认情况下不是“异常安全的"(exception safe)。具体来说,这意味着:如果抛出异常,那么本应在作用域末尾释放的对象现在却不会自动释放了。如果想生成“异常安全”的代码,可以通过设置编译器的标志来实现,不过这将引入一些额外代码,在不抛出异常时,也照样要执行这部分代码。需要打开的编译器标志叫做-fobjc-arc-exceptions

但是我们应该注意,Objective-C语言只有在极其罕见的情况下拋出异常,异常拋出之后,无须考虑恢复问题,而且应用程序此时也应该退出。这就是说,不用再编写复杂的“异常安全”代码了。

异常只应该用于极其严重的错误,比如说,你编写了某个抽象基类,它的正确用法是先从中继承一个子类,然后使用这个子类。在这种情况下,如果有人直接使用了这个抽象基类,那么可以考虑抛出异常。

与其他语言不同,Objective-C中没办法将某个类标识为“抽象 类”。要想达成类似效果,最好的办法是在那些子类必须覆写的超类方法里抛出异常。这样的话,只要有人直接创建抽象基类的实例并使用它,即会拋出异常:

-(void)mustOverrideMethod {
NSString *reason = [NSStringstringWithFormat: 
                    @"%@ must be overridden",
                    NSStringFromSelector(_cmd)];
@throw [NSException
    exceptionWithName:NSInternalInconsistencyException 
    reason:reason 
    userInfo:nil];
}

既然异常只用于处理严重错误(fatal error,致命错误),那么对其他错误怎么办呢?在出 现“不那么严重的错误"(nonfatal error,非致命错误)时,Objective-C语言所用的编程范式为: 令方法返回nil/0,或是使用NSError,以表明其中有错误发生。例如,如果初始化方法无法根据传入的参数来初始化当前实例,那么就可以令其返回nil/0:

-(id)initWithValue:(id)value { 
    if ((self = [super init])){
        if ( /* Value means instance can11 be created */ ) { 
        self = nil;
     } else {
        // Initialize instance
        }
     }
    return self;
}

在这种情况下,如果if语句发现无法用传人的参数值来初始化当前实例(比如这个方法 要求传入的value参数必须是non-nil的),那么就把self设置成nil,这样的话,整个方法的 返回值也就是nil了。调用者发现初始化方法并没有把实例创建好,于是便可确定其中发生了错误。

NSError的用法更加灵活,因为经由此对象,我们可以把导致错误的原因回报给调用者。 NSError对象里封装了三条信息:

  • Error domain(错误范围,其类型为字符串)
    错误发生的范围。也就是产生错误的根源,通常用一个特有的全局变量来定义。比方说,“处理URL的子系统”(URL-handling subsystem)在从URL中解析或取得数据时如果出错了,那么就会使用NSURLErrorDomain来表示错误范围。

  • Error code(错误码,其类型为整数)
    独有的错误代码,用以指明在某个范围内具体发生了何种错误。某个特定范围内可能会发生一系列相关错误,这些错误情况通常采用enum来定义。例如,当HTTP请求出错时,可能会把HTTP状态码设为错误码。

  • Uesr info(用户信息,其类型为字典)
    有关此错误的额外信息,其中或许包含一段“本地化的描述”(localized description), 或许还含有导致该错误发生的另外一个错误,经由此种信息,可将相关错误串成一条 “错误链”(chain of errors)

NSError的一种常见用法是,经由方法的“输出参数”返回给调用者。比如像这样:

-(BOOL)doSomething: (NSError**)error

用例为:

NSError *error = nil;
BOOL ret = [object doSomething:&error];
    if (error) {
//There was an error
}

也可以通过委托协议来传递此错误。有错误发生时,当前对象会把错误信息经由协议中的某个方法传给其委托对象(delegate)。这里不做过多说明。

要点

  • 只有发生了可使整个应用程序崩溃的严重错误时,才应使用异常。
  • 在错误不那么严重的情况下,可以指派“委托方法”(delegate method)来处理错误,也可以把错误信息放在NSError对象里,经由“输出参数”返回给调用者。