Error in Swift class: Property not initialized at super.init call
I have two classes, Shape
and Square
class Shape {
var numberOfSides = 0
var name: String
init(name:String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
class Square: Shape {
var sideLength: Double
init(sideLength:Double, name:String) {
super.init(name:name) // Error here
self.sideLength = sideLength
numberOfSides = 4
}
func area () -> Double {
return sideLength * sideLength
}
}
With the implementation above I get the error:
property 'self.sideLength' not initialized at super.init call
super.init(name:name)
Why do I have to set self.sideLength
before calling super.init
?
Solution 1:
Quote from The Swift Programming Language, which answers your question:
“Swift’s compiler performs four helpful safety-checks to make sure that two-phase initialization is completed without error:”
Safety check 1 “A designated initializer must ensure that all of the “properties introduced by its class are initialized before it delegates up to a superclass initializer.”
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11
Solution 2:
Swift has a very clear, specific sequence of operations that are done in initializers. Let's start with some basic examples and work our way up to a general case.
Let's take an object A. We'll define it as follows.
class A {
var x: Int
init(x: Int) {
self.x = x
}
}
Notice that A does not have a superclass, so it cannot call a super.init() function as it does not exist.
OK, so now let's subclass A with a new class named B.
class B: A {
var y: Int
init(x: Int, y: Int) {
self.y = y
super.init(x: x)
}
}
This is a departure from Objective-C where [super init]
would typically be called first before anything else. Not so in Swift. You are responsible for ensuring that your instance variables are in a consistent state before you do anything else, including calling methods (which includes your superclass' initializer).
Solution 3:
From the docs
Safety check 1
A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.
Why do we need a safety check like this?
To answer this lets go though the initialization process in swift.
Two-Phase Initialization
Class initialization in Swift is a two-phase process. In the first phase, each stored property is assigned an initial value by the class that introduced it. Once the initial state for every stored property has been determined, the second phase begins, and each class is given the opportunity to customize its stored properties further before the new instance is considered ready for use.
The use of a two-phase initialization process makes initialization safe, while still giving complete flexibility to each class in a class hierarchy. Two-phase initialization prevents property values from being accessed before they are initialized, and prevents property values from being set to a different value by another initializer unexpectedly.
So, to make sure the two step initialization process is done as defined above, there are four safety checks, one of them is,
Safety check 1
A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.
Now, the two phase initialization never talks about order, but this safety check, introduces super.init
to be ordered, after the initialization of all the properties.
Safety check 1 might seem irrelevant as, Two-phase initialization prevents property values from being accessed before they are initialized can be satisfied, without this safety check 1.
Like in this sample
class Shape {
var name: String
var sides : Int
init(sides:Int, named: String) {
self.sides = sides
self.name = named
}
}
class Triangle: Shape {
var hypotenuse: Int
init(hypotenuse:Int) {
super.init(sides: 3, named: "Triangle")
self.hypotenuse = hypotenuse
}
}
Triangle.init
has initialized, every property before being used. So Safety check 1 seems irrelevant,
But then there could be another scenario, a little bit complex,
class Shape {
var name: String
var sides : Int
init(sides:Int, named: String) {
self.sides = sides
self.name = named
printShapeDescription()
}
func printShapeDescription() {
print("Shape Name :\(self.name)")
print("Sides :\(self.sides)")
}
}
class Triangle: Shape {
var hypotenuse: Int
init(hypotenuse:Int) {
self.hypotenuse = hypotenuse
super.init(sides: 3, named: "Triangle")
}
override func printShapeDescription() {
super.printShapeDescription()
print("Hypotenuse :\(self.hypotenuse)")
}
}
let triangle = Triangle(hypotenuse: 12)
Output :
Shape Name :Triangle
Sides :3
Hypotenuse :12
Here if we had called the super.init
before setting the hypotenuse
, the super.init
call would then have called the printShapeDescription()
and since that has been overridden it would first fallback to Triangle class implementation of printShapeDescription()
. The printShapeDescription()
of Triangle class access the hypotenuse
a non optional property that still has not been initialised. And this is not allowed as Two-phase initialization prevents property values from being accessed before they are initialized
So make sure the Two phase initialization is done as defined, there needs to be a specific order of calling super.init
, and that is, after initializing all the properties introduced by self
class, thus we need a Safety check 1
Solution 4:
The "super.init()" should be called after you initialize all your instance variables.
In Apple's "Intermediate Swift" video (you can find it in Apple Developer video resource page https://developer.apple.com/videos/wwdc/2014/), at about 28:40, it is explicit said that all initializers in super class must be called AFTER you initialize your instance variables.
In Objective-C, it was the reverse. In Swift, since all properties need to be initialized before it's used, we need to initialize properties first. This is meant to prevent a call to overrided function from super class's "init()" method, without initializing properties first.
So the implementation of "Square" should be:
class Square: Shape {
var sideLength: Double
init(sideLength:Double, name:String) {
self.sideLength = sideLength
numberOfSides = 4
super.init(name:name) // Correct position for "super.init()"
}
func area () -> Double {
return sideLength * sideLength
}
}