I have UIViewController
subclass. I have a constant that I wanted to lazy load. To do this I used a function to set it's default value. I have the two init
methods in here because I was previously using them to set the value. Then I found that I can not call a common method to set the value. It seems that only init methods can set the value of a class constant. When I created the function to set the default value, I found that I have an infinite loop that repeatedly calls my function for goal from the init(nibName, bundle) function. I'm new to swift and not sure what I'm doing wrong.
class ViewController: UIViewController {
let goal: Goal =
{
return Goal()
}()
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
{
super.init()
}
required init(coder decoder: NSCoder)
{
super.init()
}
}
When you override a methods you should call super implementation of this method.
required override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
The constant let
values can be set only in initializers. If you have more than 1 initializer think how you can chain them. It's rely useful to have 1 designated initialiser and many convenience. In this way you would set you variables only in 1 place. Example
class A {
let age: Int
let name: String
let type: String
init(age: Int, name: String, type: String) {
self.age = age
self.type = type
self.name = name
}
convenience init() {
self.init(age: 0, name: "", type: "")
}
convenience init(age: Int) {
self.init(age: age, name: "", type: "")
}
convenience init(age: Int, name: String) {
self.init(age: age, name: name, type: "")
}
In swift you have to fully initialize class in init method. It means you have to set values for all your properties and call super init, if it exist. After that you can access self and call methods.
In the Cocoa Framework classes have more then 1 way to instantiate them and have many initialiser. Example UIViewController has
init(nibName: String?, bundle: NSBundle?)
and init(coder :NSCoder)
How we would initialize this class ?
This solution has 1 huge disadvantage - It's not DRY
You have the same code in 2 places. If you need to change it or fix it, you need to do the same in 2 places. Really bad architecture and code smell.
var name: String
required init(coder aDecoder: NSCoder) {
name = "Name"
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
name = "Name"
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
Can we do better? The solution looks obvious - Extract name initialisation in separate method. But we can't do that because of Swift class safety rule
Initialization Steps
Code:
required init(coder aDecoder: NSCoder) {
setDefaultName() // Error. Can't access self yet
super.init(coder: aDecoder)
}
func setDefaultName() {
name = "Name"
}
Set default value for variable.
This looks better because we initialize property only in one place.
var name: String = ""
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
If you need more complicated logic to initialize your variable you can use closure for that.
I don't like that because now you have logic (a closure in the same as function) in you class variable declaration.
Can we do better? Yes we can!
var name: String = {
var name = "Name"
// ....
// Other complicated logic
name + " Is Best"
return name
}()
It would be better to take that closure and move it outside class variable definition,
so we can do something like that var name = Factory.defaultName()
Swift support inner classes. You can create a class inside a class. This way you can group some functionality inside a class. This sound perfect to group initialisers methods into Factory class
Final Example:
var name: String = Factory.defaultName()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
class Factory {
class func defaultName () -> String {
return "Name"
}
}