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 releaseself
(if not using ARC) and return nil: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 thedealloc
message to the object. And yourdealloc
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: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 toCFRelease
), 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 ofinit
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: