从[[class alloc] init]返回nil的后续操作

时间:2012-11-16 01:34:43

标签: objective-c cocoa automatic-ref-counting idioms

作为对Is returning nil from a [[class alloc] init] considered good practice?的分类的跟进,有一种情况我没有看到任何讨论过多的问题:如何处理一个在调用下一个init之前失去一些先决条件的init?

示例,假设在此initWithStuff:方法中传递nil或者通常没有值传递给initWithValue:绝对失败,我们肯定想返回nil。

- (id)initWithStuff:(Stuff *)inStuff {
  if (!inStuff || ![inStuff hasValidValue])
  {
    // can't proceed to call initWithValue: because we have no value
    // so do what?
    return nil;
  }
  NSInteger value = [inStuff integerValue];
  return [super initWithValue:value];
}

也许一个更清晰的例子是,如果我们包装的指定初始化方法接受一个对象指针,并且如果它传递了nil则抛出异常。我们肯定需要将导致异常的init调用短路。

我的猜测:以任何可能的方式初始化,然后在返回nil之前释放self。如有必要,请调用裸init或任何其他初始化程序,以便在释放之前完成将self置于已知状态。

  // can't proceed to call super's initWithValue: because we have no value
  // so do what? do this:
  self = [super init]; // or initWithValue:0
  [self release];
  return nil;

如果没有这样的初始化器可以在没有有效数据的情况下工作,我想有人需要构建一些有效的伪数据。或者向其作者抱怨,直到那时只返回零并与泄漏一起生活:^)

此外,ARC如何影响这种情况?

我的猜测:仍然以任何可能的方式完成初始化,然后返回零。你认为设置自我可能是多余的,但在某些情况下并非如此。在任何情况下,它都需要在那里使编译器警告静音。

  // can't proceed to call super's initWithValue: because we have no value
  // so do what? do this:
  self = [super init]; // finish init so ARC can release it having no strong references
  return nil;

我的猜测是否有任何错误?

1 个答案:

答案 0 :(得分:1)

理想情况下,如果前提条件失败,则不要调用[super init…]。您只需发布self(如果不使用ARC)并返回nil:

- (id)initWithStuff:(Stuff *)stuff {
    if (!stuff || ![stuff isValid]) {
        [self release]; // if not using ARC
        return nil;
    }

    if (self = [super init]) {
        // initialization here
    }
    return self;
}

该版本负责在MRC下解除分配self。在ARC下,编译器将为您插入发行版。

但是,此方法存在潜在问题。当您发布self时(或当ARC为您发布)时,系统将发送dealloc消息对象。您的dealloc方法会调用[super dealloc]。您可以取消MRC下的[super dealloc],但无法通过ARC来避免它。

因此,危险在于您的超类可能会假设其实例变量之一已初始化,并依赖其dealloc中的初始化值。例如,假设这是超类:

@interface SomeSuperclass : NSObject
@end

@implementation SomeSuperclass {
    CFMutableBagRef bag;
}

- (id)init {
    if (self = [super init]) {
        bag = CFBagCreateMutable(NULL, 0, &kCFTypeBagCallBacks);
    }
    return self;
}

- (void)dealloc {
    CFRelease(bag);
}

@end

这里的问题是CFRelease要求其参数不是零。因此,如果不在子类中调用[super init],这将在解除分配期间崩溃。

鉴于这个问题,我必须改变我的初步建议。如果你知道你的超类dealloc没有这种问题(例如,因为它在取消引用它们或将它们传递给CFRelease之前检查指针),那么你就可以放心地调用{{ 1}}。

如果您知道您的超类[super init]是安全的,那么我的建议是将您的前置条件从dealloc移出并进入班级工厂方法。

换句话说,不要将init视为班级公共界面的一部分。提供用于创建实例的类方法:

alloc/init