Error in Swift class: Property not initialized at

2019-01-01 06:35发布

问题:

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?

回答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



回答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).



回答3:

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
    }
}


回答4:

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



回答5:

Sorry for ugly formatting. Just put a question character after declaration and everything will be ok. A question tells the compiler that the value is optional.

class Square: Shape {
    var sideLength: Double?   // <=== like this ..

    init(sideLength:Double, name:String) {
        super.init(name:name) // Error here
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Edit1:

There is a better way to skip this error. According to jmaschad\'s comment there is no reason to use optional in your case cause optionals are not comfortable in use and You always have to check if optional is not nil before accessing it. So all you have to do is to initialize member after declaration:

class Square: Shape {
    var sideLength: Double=Double()   

    init(sideLength:Double, name:String) {
        super.init(name:name)
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Edit2:

After two minuses got on this answer I found even better way. If you want class member to be initialized in your constructor you must assign initial value to it inside contructor and before super.init() call. Like this:

class Square: Shape {
    var sideLength: Double  

    init(sideLength:Double, name:String) {
        self.sideLength = sideLength   // <= before super.init call..
        super.init(name:name)
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Good luck in learning Swift.



回答6:

swift enforces you to initialise every member var before it is ever/might ever be used. Since it can\'t be sure what happens when it is supers turn, it errors out: better safe than sorry



回答7:

Edward,

You can modify the code in your example like this:

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}

This is using an implicitly unwrapped optional.

In documentation we can read:

\"As with optionals, if you don’t provide an initial value when you declare an implicitly unwrapped optional variable or property, it’s value automatically defaults to nil.\"



回答8:

Swift will not allow you to initialise super class with out initialising the properties, reverse of Obj C. So you have to initialise all properties before calling \"super.init\".

Please go to http://blog.scottlogic.com/2014/11/20/swift-initialisation.html. It gives a nice explanation to your problem.



回答9:

Add nil to the end of the declaration.


// Must be nil or swift complains
var someProtocol:SomeProtocol? = nil

// Init the view
override init(frame: CGRect)
    super.init(frame: frame)
    ...

This worked for my case, but may not work for yours



回答10:

You are just initing in the wrong order.

     class Shape2 {
        var numberOfSides = 0
        var name: String
        init(name:String) {
            self.name = name
        }
        func simpleDescription() -> String {
            return \"A shape with \\(numberOfSides) sides.\"
        }
    }

    class Square2: Shape2 {
        var sideLength: Double

        init(sideLength:Double, name:String) {

            self.sideLength = sideLength
            super.init(name:name) // It should be behind \"self.sideLength = sideLength\"
            numberOfSides = 4
        }
        func area () -> Double {
            return sideLength * sideLength
        }
    }


回答11:

It\'s a staggeringly stupid design.

Consider something like this:

.
.
.
var playerShip:PlayerShip
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100))
    playerLayerNode.addChild(playerShip)        
}
.
.
.

This is invalid, as noted above. But so is:

.
.
.
var playerShip:PlayerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100))
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}
.
.
.

Because \'self\' hasn\'t be initialised.

I sincerely hope this bug is fixed soon.

(Yes I know I could create an empty object and then set the size but that\'s just stupid).