Sebastian Kirsche
Sebastian Kirsche

Reputation: 873

Why does the Swift default initializer of a UIViewController subclass initialize the properties twice?

Why does the Swift default initializer init() of a UIViewController subclass initialize the properties twice? The same thing happens with subclasses of UIView, but not with a direct subclass of NSObject.

The problem goes away by using Parent(nibName: nil, bundle: nil) instead of Parent() for initialization. It also works correctly when I provide custom initializers for Parent.

I know how to work around this problem, but I am curious about why it happens.

The problem can be reproduced by copying this code into an Xcode 6.0.1 Playground.

import UIKit

class Child {
    init() {
        println("Child init")
    }
}

class Parent: UIViewController {
    let child = Child()
}

// This way "Child init" is printed twice:
let parent = Parent()

// This way "Child init" is printed once:
//let parent = Parent(nibName: nil, bundle: nil)



Update: When I define a fake class that has similar initializers like the ones UIViewController has and use that as the superclass of Parent both ways to initialize it work and print "Child init" only once.

import UIKit

class Child {
    init() {
        println("Child init")
    }
}

class FakeViewController : UIResponder {
    init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {

    }

    convenience override init() {
        self.init(nibName: nil, bundle: nil)
    }
}

class Parent: FakeViewController {
    let child = Child()
}

// With the FakeViewController both initializers cause "Child init" to be printed once:
let parent = Parent()
//let parent = Parent(nibName: nil, bundle: nil)


Upvotes: 3

Views: 3132

Answers (3)

Sebastian Kirsche
Sebastian Kirsche

Reputation: 873

This problem has been fixed in Xcode 6.3. The last version where I can reproduce the bug is Xcode 6.2.

Upvotes: 1

rintaro
rintaro

Reputation: 51911

Apparently, UIViewContoller's init is implemented like:

- (instancetype)init {
    self = [super init]; // <- not sure
    if(self) {
        self = [[self.class alloc] initWithNibName:nil bundle:nil];
    }
    return self;
}

You can see, with debugger, that self of Parent has different address between first Child() call and second one.

In Swift, properties are initialised before the owner object is initialised. that is why your Child() is get called twice.

Upvotes: 1

AlBlue
AlBlue

Reputation: 24060

The first print occurs when constructing the Parent instance; all of the instance fields are initialized at that point, which includes creating the Child instance.

The second print occurs when the implicit super.init is called of the Parent. Given that the code for this is closed, it's impossible to know for sure what is happening; but the problem probably stems from the fact that the init is a convenience initializer in UIViewcontroller (the designated initializer is init:nibName:bundle). Documentation in the UIVIewController states that when it is overridden, the required initializer must be called.

So to correct this, you need to add:

class Parent: UIViewController {
  override init() {
    super.init(nibName:nil,bundle:nil)
  }
  // the following is also required if implementing an initializer
  required init(coder:NSCoder) {
    super.init(coder:coder)
  }
}

See https://developer.apple.com/library/prerelease/iOS/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_319 for more information about designated vs convenience initializers.

Upvotes: 4

Related Questions