Can I use assert() to catch failure of [super init]? - objective-c

A typical init method in Objective C returns nil if initialisation fails:
- (id) init {
self = [super init];
if (self) {
// more initialisation
}
return self;
}
Would it make sense to use assertions to catch initialisations which should
always succeed? i.e:
- (id) init {
self = [super init];
assert(self); // unrecoverable?
// more initialisation
array = [[NSArray alloc] init];
assert(array); // unrecoverable?
return self;
}
In this case, failure of [super init] is defined (assumed?) to be an unrecoverable error, so crashing with an assertion failure is warranted.
It seems that the convention of returning nil is intended to be used to allow for error recovery, but in situations where error recovery isn't possible, is it reasonable to just throw an assertion?
Thanks...
Edit:
And see also this question

No returning nil, as per the normal pattern, is sufficient. It's up to the caller to decide what to do about it:
id obj = [[SomeObject alloc] init];
if (!obj)
[NSException raise:#"Oops"];

Related

Objective C Constructer without init

When I'm creating custom classes, I'd like to be able to skip the alloc init part of the code once I go to construct an instance of the class. Similar to how it's done with:
NSString * ex = [NSString stringWithFormat...];
Basically I already have the class set up with a custom initializer method to set up my basic variables. However, when I'm on the front end and actually making these critters I have to say:
[[Monster alloc] initWithAttack:50 andDefense:45];
and I'd rather be able to say
[Monster monsterWithAttack:50 andDefense:45];
I know it's a simple stupid thing to just get rid of the alloc part but it makes the code more readable so I'd prefer to do it that way. I originally tried just changing my method from
-(id)initWithAttack:(int) a andDefense:(int) d
to
-(id)monsterWithAttack:(int) a andDefense:(int) d
and then changing my self = [super init] to self = [[super alloc] init]; but that clearly doesn't work! Any ideas?
You have to make a class method
+(id)monsterWithAttack:(int) a andDefense:(int) d
in which you create, initialize, and return an instance (and don't forget your memory management):
+(id)monsterWithAttack:(int) a andDefense:(int) d {
// Drop the autorelease IF you're using ARC
return [[[Monster alloc] initWithAttack:a andDefense:d] autorelease];
}
What you want is a convenience constructor. It's a class method that returns a useable instance of a class and allocates memory for it at the same time.
-(id)initWithAttack:(int)a andDefense:(int)d;
+(id)monsterWithAttack:(int)a andDefense:(int)d;
+(id)monsterWithAttack:(int)a andDefense:(int)d {
//-autorelease under MRC
return [[[self class] alloc] initWithAttack:a andDefense:d];
}
-(id)initWithAttack:(int)a andDefense:(int)d {
self = [super init];
if (self){
//custom initialization
}
return self;
}
You should use a class factory method in the header of monster class.
+(id)monsterWithAttack:(int) attackValue andDefense:(int) defenseValue
in the implementetation of monster class
+(id)monsterWithAttack:(int) attackValue andDefense:(int) defenseValue {
return [[[[self class] alloc] initWithAttack:attackValue andDefense:defenseValue] autorelease];
}
The use of [self class] guarantees the correct dispatch during subclassing. If you are using ARC you can avoid the autorelease method
Class methods of this type use autorelease.
So for instance, you might say:
+ (id)
monsterWithAttack:(int) a
defense:(int) d
{
return [[Monster alloc] initWithAttack:a defense:d]
autorelease];
}

Init and init with parameters

Here is the code :
#implementation Accumulateur
// Constructor
- (id) init
{
return ([self initWithTotal:0]);
}
- (id) initWithTotal:(int)aTotal
{
AccumulateurMoyen *ac;
if ((ac = [[AccumulateurMoyen alloc] init]) == nil)
{
[self release];
return (nil);
}
return ([self initWithTotal:aTotal andAccumulateurMoyen:ac]);
}
- (id) initWithTotal:(int)aTotal
andAccumulateurMoyen:(AccumulateurMoyen *)aAcMoyen
{
if (self = [super init])
{
[aAcMoyen retain];
[acMoyen release];
acMoyen = aAcMoyen;
total = aTotal;
}
return (self);
}
#end
The problem is here : if ((ac = [[AccumulateurMoyen alloc] init]) == nil)
As I redefined init, the init called is mine and not that of NSObject...
I dont have idea, how i can do that correctly (AccumulateurMoyen is subclass of Accumulateur)
Thx you
You have probably undesired recursion there: [[AccumulateurMoyen alloc] init] tries to create new AccumulateurMoyen but that results in nested initWithTotal: which again tries to create another AccumulateurMoyen instance etc.
I.e. your code tries to create Accumulateur which has member acMoyen set to new instance of AccumulateurMoyen, which again has its acMoyen set to another new instance of AccumulateurMoyen etc.
You must to break the endless recursion. E.g. in initWithTotal:, replace the line
if ((ac = [[AccumulateurMoyen alloc] init]) == nil)
with
if ((ac = [[AccumulateurMoyen alloc] initWithTotal:0 andAccumulateurMoyen:nil]) == nil)
I.e. the nested AccumulateurMoyen will have its member set to nil.
Sorry but I think you have a structural problem here. Why your super class need to have an reference to a class that extend it? I think your best option to it is think again how your class structure will be.
But in your subClass you can change the init method to so your problem will disappear.
(id)init {
return ([NSObject init]);
}

Obj-C, Returning 'self' while it is not set to the result of '[(super or self) init...]'?

I'm getting an analyzer warning, since upgrading...
Returning 'self' while it is not set to the result of '[(super or self) init...]'
Dunno whats wrong with it ?
- (id)initWithFrame:(CGRect)frame {
if (self == [super initWithFrame:frame]) {
[self initLayers];
}
return self;
}
Get rid of the second equals sign. The proper if statement is:
if(self = [super initWithFrame:frame])
The point of this is that the super implementation could return a different, but still valid, object than the current value of self. In this case, your if statement will be false, since the objects are different, and so your initialization won't occur. However, since it returned a different object, the super implementation should have released the old self, which is what you are returning. This means you are probably returning an invalid pointer.
By using only one equals sign, you set the variable instead of comparing it. Since if(object) is true if object is not nil, it is equivalent to this:
if((self = [super initWithFrame:frame]) != nil)
Or, the easier to understand version:
self = [super initWithFrame:frame];
if(self != nil)
This code reassigns self to be the value returned by the super initializer, instead of just assuming the value returned is the same. This is the same reason why it is important to set a variable to the result of the init... method and not alloc.
// good
id object = [[MyClass alloc] init];
// bad
id object = [MyClass alloc];
[object init];
If I recall correctly the syntax is self = ... not self == .... The syntax uses the returned value from assignment.
your returning self not initialized
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initLayers];
}
return self;
}

What to do with useless init?

This is currently what I have for my init,
- (id)init
{
self = [super init];
if (self) {
self.url = [[NSURL alloc] init];
self.blurb = [[NSString alloc] init];
self.author = [[NSString alloc] init];
}
return self;
}
It does nothing, but I have another method called initWithObject: that will use its argument to fill up the instance variables url, blurb, and author. I don't know what I should be doing with this init. Should I throw an exception? What other options do I have?
If you want to override your standard -init method you could either return nil (if you do not want -init to be used) or do:
- (instancetype)init
{
return [self initWithObject:nil];
}
If you want to stop the use of -init completely you can tag it as an unavailable attribute or use NSAssert:
// .h
// ...
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
// ...
// .m
- (instancetype)init
{
NSAssert(NO, #"This method should not be used");
return nil;
}
You can use either UNAVAILABLE_ATTRIBUTE or NSAssert(), but if you use UNAVAILABLE_ATTRIBUTE you need some kind of implementation of -init, even if it just returns nil.
You don't have to have a plain init—you can simply have your initWithObject: method. If you're doing any basic setup that will remain the same 80% of the time, or if you have some common code in all your initializers, you can override init, but you are not required to.
Also consider changing your initWithObject: name to be more specific, to something like initWithPost: (I'm assuming this is some kind of blog-entry fetcher based on your ivars) so it's more apparent what object is desired.
I think you misinterpreted what you read. I don't think you would throw an exception. You could; however, leak memory. If your initWithObject: method looks like this:
- (id)initWithObject:(id)obj {
if ((self = [self init])) {
self.url=[obj url];
self.blurb=[obj blurb];
self.author=[obj author];
}
return self;
}
And you would be perfectly fine. You could get an exception if your object was instantiated with -init and you used a variable which was assigned, assuming it was real. So in your subsequent methods be sure to check that the objects exist before using them.
If you made your object with -init rather than -initWithObject this could throw an exception:
- (void)dealloc {
[url release];
[blurb release];
[author release];
[super dealloc];
}
The rule that Apple has established for Cocoa programming is that every class must have one initializer method which is the "Designated Initializer". Every other initializer for the class must call that D.I.* The D.I. itself must call the superclass's D.I. Generally, the initializer with the greatest number of arguments (the one that most completely specifies the state of the new object) is the D.I.
In your case, with the bare init, and initWithObject:, the second would likely be the D.I. You would therefore override init to call initWithObject: with some default argument:
- (id) init {
return [self initWithObject:[Object objectWithURL:[NSURL URLWithString:#"http://www.apple.com"]
blurb:#""
author:#""]];
}
This will result in a sort of dummy object, which is correctly initialized with useless data. (Outside of ARC, be sure to watch the memory management of the default argument(s) -- you want to use an autoreleased/unowned object(s).)
*Sometimes an exception is made for initWithCoder:.
If you have any method that you don't want called and that you don't want your subclass to support, throwing an exception in a Debug build is perfectly reasonable.

What happens to alloc if initWithX fails?

It is common practice to write MyClass* obj = [[MyClass alloc] initWithX:X] in Objective-C. initWithX is usually defined as
- (MyClass*) initWithX: (MyArgClass*) X {
if (self = [super init]) {
// initialize
}
return self;
}
My question is: what if initialize fails? I don't want to throw exceptions, but, how do I indicate error? If I return nil, the caller will not be able to release the pointer.
If initialization fails for any reason you should release self. For an exception that may occur in your initialization you need to add you #try #catch as appropriate so you can release self.
- (MyClass*) initWithX: (MyArgClass*) X {
if (self = [super init]) {
// initialize
if(myInitializationCodeFailed)
{
[self release];
return nil;
}
}
return self;
}
Update
If it is possible for your initialization fail I would not raise an exception from with in your initialization code. If you would like to provide the caller with information I would refactor the initializer to accept an NSError to be returned.
- (MyClass*) initWithX: (MyArgClass*) X error:(NSError**)error {
As Alexei Sholik points in the comments check out the Handling Initialization Failure section of Allocating and Initializing Objects.
Basically, this answers your question.
Handling Initialization Failure
In general, if there is a problem during an initialization method, you should call the release method on self and return nil.
There are two main consequences of this policy:
Any object (whether your own class, a subclass, or an external caller) that receives nil from an initializer method should be able to deal with it. In the unlikely case that the caller has established any external references to the object before the call, you must undo any connections.
You must make sure that dealloc methods are safe in the presence of partially initialized objects.
...