Reputation: 873
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)
let parent = Parent()
in the first example should not even compile?Upvotes: 3
Views: 3132
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
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
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