6

Some special classes like UIView have more than one designated initializer.

In Objective-X, we could factor common initialization into a separate function

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [self initialize];
    }

    return self;
}

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self initialize];
    }
    return self;
}

In Swift this is no longer a possibility because the following code results 'self used before super.init call'

override init(frame: CGRect) {
    self.initialize()
    super.init(frame: frame)
}

required init(coder aDecoder: NSCoder) {
    self.initialize()
    super.init(coder: aDecoder)
}

Placing self.initialize after super.init is no help, either, since my entire purpose is initializing members. The following would result in 'property self.X not initialized at super.init call'

var X : Int

override init(frame: CGRect) {
    super.init(frame: frame)
    self.initialize()
}

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.initialize()
}

What should i do if i want to factor common initialization in this case? Note that i specifically do not want to use awakeFromNib because i want my objects to be available in any related awakeFromNib implementations in my object hierarchy. Note also that optionals do not make sense for my use case.

Talha
  • 903
  • 8
  • 31
graveley
  • 311
  • 3
  • 5

2 Answers2

2

The obvious answer is to call initialize after calling super.init:

override init(frame: CGRect) {
    super.init(frame: frame)
    self.initialize()
}

However, you probably have some instance variables that you want to initialize in the initialize method. The facts are these:

  1. You must initialize all member variables before calling super.
  2. You may not call methods on self (or access computed properties on self) before calling super.init.

The inexorable conclusion is that if you have instance variables you want to initialize in initialize, you must declare each such variable with var, not let, and do one of these things:

  1. Give the instance variable a default value:

    var name: String = "Fred"
    
  2. Make the instance variable Optional, and let it be initialized to nil:

    var name: String?
    
  3. Make the instance variable ImplicitlyUnwrappedOptional, and let it be initialized to nil:

    var name: String!
    

My preference at this point is #2 (make it Optional and initialized to nil).

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • This is for the case that i want a nonoptional variable. It appears the only solution in that case is to use a default value. However, this does not cover the case of interdependence among defaults. For example, what if i have a nonoptional Y that depends on whatever happens to be nonoptional X's default? – graveley Sep 30 '14 at 21:02
  • An instance variable's initializer expression (in the variable declaration) cannot refer to `self`, any other instance variable, or any `init` argument. That's just the way it is. Yes, it sucks. – rob mayoff Sep 30 '14 at 21:07
  • 1
    So, then, certain functionality is simply impossible if you want to use nonoptionals. You have to adjust your type choices to be something you never actually wanted just to align with the languages limitations. The fact that those cases exist also means that, sometimes, when refactoring or adding features, you will need to change your nonoptionals into optionals just to make things work correctly. So people will just end up using optionals in cases like this, even for things that are always guaranteed. – graveley Sep 30 '14 at 21:56
  • Exactly. Like I said, it sucks. – rob mayoff Sep 30 '14 at 22:22
  • Ok, well can you add the part about how it sucks to your answer and then i'll accept it. Basically, the answer is that there is no good answer for the use case mentioned. – graveley Oct 01 '14 at 00:19
  • Swift now lets you declare implicit unwrapped optionals as `let`. Using that actually maps very closely to the unsafe behaviors of ObjC initialization. Swift is just making it more explicit what was always unsafe. So far, I've found Option 1 often is the quite appropriate, and that Option 2 often captures what you really meant, but Option 3 is a useful fallback in some corner cases. – Rob Napier Oct 01 '14 at 13:09
  • The main thing here is that Swift can't prove that every `init` (including `init` of subclasses) will certainly call `initialize`. So what is it supposed to do if you *don't* call `initialize` in a subclass? (I can imagine the Swift compiler getting smarter about this in the future for `final` classes and structs, where it can see all the code, but for open classes, I don't think it's solvable while maintaining the safety promise.) – Rob Napier Oct 01 '14 at 13:23
  • One nice api fix would be to ensure all classes have only one designated initializer. – graveley Oct 03 '14 at 23:44
0

There's a neat solution to this here: https://stackoverflow.com/a/26772103/2420477

Basically, mark your two init functions convenience and then have them call out to a third private init function that has your common initialisation code.

For your case, you could use something like this:

class MyView: UIView {

    let X: Int // No need to use an optional, as we assign this in an initializer

    enum InitMethod {
        case Coder(NSCoder)
        case Frame(CGRect)
    }

    override convenience init(frame: CGRect) { // Marking as convenience let's us call out to another constructor
        self.init(.Frame(frame))!
    }

    required convenience init?(coder aDecoder: NSCoder) { // Marking as convenience let's us call out to another constructor
        self.init(.Coder(aDecoder))
    }

    private init?(_ initMethod: InitMethod) {
        // You can put your common initialization code here:
        X = 1

        switch initMethod {
            case let .Coder(coder): super.init(coder: coder)
            case let .Frame(frame): super.init(frame: frame)
        }
    }
}
Community
  • 1
  • 1
ramin
  • 1,182
  • 1
  • 7
  • 5