后续从[[class alloc] init]返回nil] [英] Followup to returning nil from a [[class alloc] init]

查看:143
本文介绍了后续从[[class alloc] init]返回nil]的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

作为从一个[[class alloc] init]返回nil被认为是好的做法?,有一个案例,我没有看到任何讨论很多:如果做一个初始化失败的前提条件,下一个init?



示例,假设在这个initWithStuff:方法被传递nil或者一般没有值传递给initWithValue:是一个绝对失败,我们一定要return nil。

   - (id)initWithStuff:(Stuff *)inStuff {
if(!inStuff ||! [inStuff hasValidValue])
{
//无法继续调用initWithValue:因为我们没有值
//那么做什么?
return nil;
}
NSInteger value = [inStuff integerValue];
return [super initWithValue:value];
}

也许一个更清楚的例子是如果指定的初始化方法我们包装一个对象指针并且如果其传递的nil引发异常。我们肯定需要短接引起异常的init调用。



我的猜测:任何方法可能的init,然后在返回nil之前释放self。如果需要,调用bare init或任何其他初始化器,它将工作完成将自我放入已知状态,然后释放它。

  /不能继续调用super的initWithValue:因为我们没有值
//那么做什么?这样做:
self = [super init]; //或initWithValue:0
[self release];
return nil;

如果没有这样的初始化工具没有有效的数据,我想需要构建一些有效的,伪数据。或者抱怨给它的作者,直到那时只是返回nil并与泄漏生活:^)

此外,ARC如何影响情况?



我的猜测:仍然可以通过任何方式完成init,然后返回nil。你会认为设置自我可能是多余的,但在某些情况下不是。在任何情况下,它,但它需要在那里沉默编译器警告。

  //无法继续调用super initWithValue:因为我们没有价值
//那么做什么?这样做:
self = [super init]; // finish init所以ARC可以释放它没有强引用
return nil;我的猜测是否以任何方式错了?

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

   - (id)initWithStuff:(Stuff *)stuff {
if(!stuff ||![stuff isValid]){
[self release] //如果不使用ARC
return nil;
}

if(self = [super init]){
//初始化这里
}
return self;
}

发布版本会释放 self


$ b

然而,这种方法存在潜在的问题。 c $ c> 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 需要其参数不为nil。所以如果你不在你的子类中调用 [super init] ,那么这将在释放时崩溃。



问题,我不得不改变我的初始建议。如果你知道你的超类的 dealloc 没有这种问题(因为,例如,它检查指针之前解除引用或传递给 CFrelease ),那么你可以安全地不调用 [super init]



不要知道您的超类的 dealloc 是安全的,那么我的建议是您将您的前提条件移出 init 并转换为类工厂方法。



换句话说,不要处理 alloc / init 作为类的公共接口的一部分。提供一个创建实例的类方法:

  //类工厂方法。在头文件中声明这一点。这是你如何
//或这个类的任何用户应该创建实例。
+(id)myObjectWithStuff:(Stuff *)stuff {
if(!stuff ||![stuff isValid])
return nil;

//这里是类对象,所以它适合发送`alloc`给它。
//你不想在这里硬编码类名,因为这将打破
//子类。
return [[self alloc] initWithStuff:stuff];
}

//这现在被认为是一个私有方法。你不应该在你的
//头文件中声明它,虽然在Objective-C你不能阻止用户调用
//如果他确定。
- (id)initWithStuff:(Stuff *)stuff {
//前提条件已经在myObjectWithStuff:中检查过了。
if(self = [super init]){
//初始化这里...
}
return self;
}


As follow-up of sorts to Is returning nil from a [[class alloc] init] considered good practice?, there's a case that I haven't seen any discussed much: what to do with an init that fails some preconditions before it can call the next init?

Example, suppose in this initWithStuff: method being passed nil or in general having no value to pass to initWithValue: is an absolute failure and we definitely want to return 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];
}

Perhaps a clearer example is if the designated initializer method we wrap takes an object pointer and throws an exception if its passed nil. We definitely need to short-circuit that init call that would cause an exception.

My guess: init by any means possible, and only then release self before returning nil. If necessary, call bare init or any other initializer that will work to finish putting self into a known state before releasing it.

  // 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;

And if there were no such initializer that will work without valid data, I guess one would need to construct some valid, dummy data. Or complain to its author and until then just return nil and live with the leak :^)

Also, how does ARC affect the situation?

My guess: still finish init by any means possible, then just return nil. You'd think setting self might be redundant, but in some cases it's not. In any case, it but it needs to be there to silence a compiler warning.

  // 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;

Are my guesses wrong in any way?

解决方案

Ideally, if a precondition fails, you don't call [super init…]. You just release self (if not using ARC) and return 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;
}

The release takes care of deallocating self under MRC. Under ARC, the compiler will insert the release for you.

However, there is a potential problem with this approach. When you release self (or when ARC releases it for you), the system will send the dealloc message to the object. And your dealloc method will call [super dealloc]. You could suppress the [super dealloc] under MRC, but you can't avoid it with ARC.

So the danger is that your superclass might assume that one of its instance variables has been initialized, and rely on that initialized value in its dealloc. For example, suppose this is the superclass:

@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

The problem here is that CFRelease requires its argument to not be nil. So this will crash during deallocation if you don't call [super init] in your subclass.

Given this problem, I have to change my initial recommendation. If you know that your superclass's dealloc doesn't have this sort of problem (because, for example, it checks pointers before dereferencing them or passing them to CFRelease), then you can safely not call [super init].

If you don't know that your superclass's dealloc is safe, then my recommendation is that you move your preconditions out of init and into a class factory method.

In other words, don't treat alloc/init as part of your class's public interface. Provide a class method for creating instances:

// The class factory method.  Declare this in your header file.  This is how you
// or any user of this class should create instances.
+ (id)myObjectWithStuff:(Stuff *)stuff {
    if (!stuff || ![stuff isValid])
        return nil;

    // self here is the class object, so it's appropriate to send `alloc` to it.
    // You don't want to hardcode the class name here because that would break
    // subclassing.
    return [[self alloc] initWithStuff:stuff];
}

// This is now considered a private method.  You should not declare it in your
// header file, though in Objective-C you can't prevent the user from calling it
// if he's determined to.
- (id)initWithStuff:(Stuff *)stuff {
    // Precondition was already checked in myObjectWithStuff:.
    if (self = [super init]) {
        // initialization here...
    }
    return self;
}

这篇关于后续从[[class alloc] init]返回nil]的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆