Why does the default Swift initializer for a UIViewController subclass initialize properties twice?
Why init()
does Swift's default initializer of a UIViewController subclass initialize 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 Parent()
for initialization. It also works correctly when I provide custom initializers for Parent
.
I know how to get around this problem, but I'm wondering why this is happening.
The issue can be reproduced by copying this code in the 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 one it UIViewController
has and uses it as a superclass from 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)
- Is it possible that the UIViewController init () function should work this way?
- Is there a bug in the UIViewController init () convenience implementation?
- Is init () a valid initializer for a UIViewController? Maybe this is not the case and
let parent = Parent()
in the first example you don't need to compile?
source to share
The first print occurs when the parent is built; all instance fields are initialized at this point, which includes creating an instance of Child.
The second print is done when the implicit one super.init
is called by the parent. Given that the code is proprietary, it is impossible to know exactly what is going on; but the problem is probably related to what init
is the convenience initializer in UIViewcontroller
(designated initializer init:nibName:bundle
). The documentation in UIVIewController says that when you override it, you must call the required initializer.
So, to fix 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)
}
}
For more information on assignable initializers vs convenience see https://developer.apple.com/library/prerelease/iOS/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097- CH18-XID_319 .
source to share
Apparently UIViewContoller
init
implemented like this:
- (instancetype)init {
self = [super init]; // <- not sure
if(self) {
self = [[self.class alloc] initWithNibName:nil bundle:nil];
}
return self;
}
You can see with the debugger that self
of Parent
has a different address between the first call Child()
and the second.
In Swift, properties are initialized before the owner object is initialized. so yours Child()
gets called twice.
source to share