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?
作为从[[class alloc] init]被认为是良好实践的Nil返回nil的后续跟进,有一种情况我没有看到任何讨论过多:如何处理init之前失败了一些先决条件打电话给下一个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.
例如,假设在这个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];
}
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.
也许一个更清晰的例子是,如果我们包装的指定初始化方法接受一个对象指针,并且如果它传递了nil则抛出异常。我们肯定需要将导致异常的init调用短路。
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.
我的猜测:以任何可能的方式初始化,然后在返回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;
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?
此外,ARC如何影响这种情况?
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?
我的猜测是否有任何错误?
1 个解决方案
#1
1
Ideally, if a precondition fails, you don't call [super init…]
. You just release self
(if not using ARC) and return nil:
理想情况下,如果前提条件失败,则不要调用[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;
}
The release takes care of deallocating self
under MRC. Under ARC, the compiler will insert the release for you.
该版本负责在MRC下解除分配自我。在ARC下,编译器将为您插入发行版。
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.
但是,这种方法存在潜在问题。当您释放self(或ARC为您释放它)时,系统会将dealloc消息发送给对象。你的dealloc方法将调用[super dealloc]。你可以抑制MRC下的[super dealloc],但你无法用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:
因此,危险在于您的超类可能会假设其实例变量之一已初始化,并依赖于其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
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.
这里的问题是CFRelease要求其参数不是零。因此,如果不在子类中调用[super init],这将在解除分配期间崩溃。
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]
.
鉴于这个问题,我必须改变我的初步建议。如果你知道你的超类的dealloc没有这种问题(因为,例如,它在取消引用它们或将它们传递给CFRelease之前检查指针),那么你可以安全地不能调用[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.
如果你不知道你的超类的dealloc是安全的,那么我的建议是你将你的前提条件从init转移到类工厂方法中。
In other words, don't treat alloc/init
as part of your class's public interface. Provide a class method for creating instances:
换句话说,不要将alloc / init视为类的公共接口的一部分。提供用于创建实例的类方法:
// 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;
}
#1
1
Ideally, if a precondition fails, you don't call [super init…]
. You just release self
(if not using ARC) and return nil:
理想情况下,如果前提条件失败,则不要调用[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;
}
The release takes care of deallocating self
under MRC. Under ARC, the compiler will insert the release for you.
该版本负责在MRC下解除分配自我。在ARC下,编译器将为您插入发行版。
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.
但是,这种方法存在潜在问题。当您释放self(或ARC为您释放它)时,系统会将dealloc消息发送给对象。你的dealloc方法将调用[super dealloc]。你可以抑制MRC下的[super dealloc],但你无法用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:
因此,危险在于您的超类可能会假设其实例变量之一已初始化,并依赖于其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
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.
这里的问题是CFRelease要求其参数不是零。因此,如果不在子类中调用[super init],这将在解除分配期间崩溃。
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]
.
鉴于这个问题,我必须改变我的初步建议。如果你知道你的超类的dealloc没有这种问题(因为,例如,它在取消引用它们或将它们传递给CFRelease之前检查指针),那么你可以安全地不能调用[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.
如果你不知道你的超类的dealloc是安全的,那么我的建议是你将你的前提条件从init转移到类工厂方法中。
In other words, don't treat alloc/init
as part of your class's public interface. Provide a class method for creating instances:
换句话说,不要将alloc / init视为类的公共接口的一部分。提供用于创建实例的类方法:
// 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;
}