6

I've been using Objective-C for a few years, but I'm still unsure how to initialize an object. Our problem is this:

-(id)init {
    self = [super init];
    if (self) {
        self.backgroundColor = [UIColor blueColor];
        self.textColor = [UIColor greenColor];
    }
    return self;
}

-(id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor yellowColor];
    }
    return self;
}

Getting it to be initialized the same in both methods means either copy and paste coding (huge no-no, causes endless problems due to human error, such as values being different, like in the example above) or a new method:

-(id)init {
    self = [super init];
    if (self) {
        [self initialSetup];
    }
    return self;
}

-(id)initialSetup {
    self.backgroundColor = [UIColor blueColor];
    self.textColor = [UIColor greenColor];
}

This then has the problem that if a subclass has an initialSetup method, the superclass calling [self initialSetup] will call the subclass's initialSetup, and thus ignore all of the superclass's setup. You can get around this by appending the class name each time, such as initialSetupTextField.

This seems rather messy though. This is all just to initialize an object. How does everyone else do this?

David Snabel-Caunt
  • 57,804
  • 13
  • 114
  • 132
Andrew
  • 15,935
  • 28
  • 121
  • 203

3 Answers3

9

Every class should have a designated initializer; the one true init... method variant that must always be called to properly initialize the class (as Matt states).

However, that doesn't fully solve the problem in that a class may also support instantiation through unarchival, which runs into the same "where do I put the common initialization goop" problem in your question.

Typically, I create a method like:

- (void)commonInit
{
     ... common initialization goop goes here ...
}

And then call it first from the designated initializers and/or initWithCoder:.

Yes, a bit messy, but not much you can do about it.

A general strategy to reduce the mess:

• provide very very few initializers; maybe init + one initializer with all the possible arguments for other configurations.

• Designate the super's initializer as your DI and make any additional initializers call through it. When subclassing where you have a more specific DI (see UIView's initWithFrame:), override the super's DI to call [self initFancy:...] which then calls [super initThroughSupersDI].

mluisbrown
  • 14,448
  • 7
  • 58
  • 86
bbum
  • 162,346
  • 23
  • 271
  • 359
8

You're missing the concept of a Designated Initializer. You write one init method that does the heavy lifting. All other initializers just call the designated initializer. Your example would look like:

-(id)init {
    return [self initWithFrame:CGRectZero];
}

-(id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor yellowColor];
    }
    return self;
}
Matt Wilding
  • 20,115
  • 3
  • 67
  • 95
  • 2
    Well stated in regards to the concept of "Designated Initializers", but doesn't cover the `initWithCoder:` case. – bbum Aug 12 '13 at 16:12
  • That's true, thanks for pointing it out. I'll defer to your answer on that point, since I would end up saying the exact same thing. – Matt Wilding Aug 12 '13 at 16:14
  • 2
    And since I refer to your answer... infinite loop! :) – bbum Aug 12 '13 at 16:20
1

…appending the class name each time, such as -initialSetupTextField

Yes, exactly that, or you can create a C function for your private instance initialization routine -- when the designated initializer is insufficient. I use a different naming scheme from yours, but as long as you use a convention which avoids collision, then you're good. I'm mentioning that because the method in your example could be confused for a getter.

Another thing I do is return a value from this routine which indicates whether there was an error (i.e. nil should be returned).

justin
  • 104,054
  • 14
  • 179
  • 226