web-dev-qa-db-ja.com

Swiftクラスのエラー:super.init呼び出しでプロパティが初期化されていません

ShapeSquareという2つのクラスがあります。

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

上記の実装では、エラーが発生します。

property 'self.sideLength' not initialized at super.init call
    super.init(name:name)

self.sideLengthを呼び出す前にsuper.initを設定しなければならないのはなぜですか?

184
JuJoDi

あなたの質問に答えるSwift Programming Languageからの引用:

「Swiftのコンパイラは、2段階の初期化がエラーなしで完了したことを確認するために4つの有用な安全チェックを実行します。」

安全性チェック1「指定された初期化子は、スーパークラス初期化子に委任する前に、そのクラスによって導入されたすべてのプロパティが初期化されていることを確認する必要があります」

抜粋:アップル社の "The Swift Programming Language"。 https://iTunes.Apple.com/us/book/Swift-programming-language/id881256329?mt=11

155
Ruben

Swiftには、イニシャライザで行われる非常に明確で具体的な一連の操作があります。いくつかの基本的な例から始めて、一般的なケースに向かって進んでいきましょう。

オブジェクトAを取りましょう。次のように定義します。

class A {
    var x: Int
    init(x: Int) {
        self.x = x
    }
}

Aにはスーパークラスがないため、super.init()関数は存在しないため、呼び出すことができません。

それでは、AというサブクラスにBという新しいクラスを追加しましょう。

class B: A {
    var y: Int
    init(x: Int, y: Int) {
        self.y = y
        super.init(x: x)
    }
}

これは、[super init]が通常他のものよりも先に呼び出されるObjective-Cからの逸脱です。 Swiftでは違います。メソッドの呼び出し(スーパークラスの初期化子を含む)を含む他のことをする前に、インスタンス変数が一貫した状態にあることを確認する責任があります。

87
Hitendra Hckr

すべてのインスタンス変数を初期化した後に "super.init()"を呼び出す必要があります。

Appleの "Intermediate Swift"ビデオ(あなたはそれをApple Developerビデオリソースページ https://developer.Apple.com/videos/wwdc/2014/ )で見つけることができます。あなたがインスタンス変数を初期化した後にスーパークラスのすべての初期化子が呼ばれなければならないとはっきり言っています。

Objective-Cでは、その逆でした。 Swiftでは、すべてのプロパティは使用前に初期化する必要があるため、最初にプロパティを初期化する必要があります。これは、最初にプロパティを初期化せずに、スーパークラスの "init()"メソッドからオーバーライドされた関数を呼び出すのを防ぐためのものです。

したがって、 "Square"の実装は次のようになります。

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

から docs

安全チェック1

指定イニシャライザは、スーパークラスイニシャライザに委任する前に、そのクラスによって導入されたすべてのプロパティが確実に初期化されるようにする必要があります。


なぜこんな安全チェックが必要なのですか?

これに答えるには、Swiftの初期化プロセスを通過します。

二相初期化

Swiftでのクラス初期化は2段階のプロセスです。最初の段階では、それぞれのストアドプロパティはそれを導入したクラスによって初期値が割り当てられます。すべての保存されたプロパティの初期状態が決定されると、2番目のフェーズが始まり、新しいインスタンスが使用可能になったと見なされる前に、各クラスにその保存されたプロパティをさらにカスタマイズする機会が与えられます。

2段階の初期化プロセスを使用すると、クラス階層内の各クラスに完全な柔軟性を与えながら、初期化を安全に実行できます。 2フェーズ初期化は、プロパティ値が初期化される前にアクセスされるのを防ぎ、他のイニシャライザによってプロパティ値が別の値に設定されるのを防ぎます。

したがって、2段階の初期化プロセスが上で定義したように行われるようにするために、4つの安全性チェックがあります。そのうちの1つは、

安全チェック1

指定イニシャライザは、スーパークラスイニシャライザに委任する前に、そのクラスによって導入されたすべてのプロパティが確実に初期化されるようにする必要があります。

さて、2段階の初期化は順序については話しませんが、この安全性チェックでは、すべてのプロパティの初期化後にsuper.initが順序付けされるようになっています。

安全性チェック1は、とは無関係に思われるかもしれません。2段階の初期化では、初期化される前にプロパティー値にアクセスできないようにします

このサンプルのように

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は初期化されています。使用される前のすべてのプロパティ。安全チェック1は無関係なので、

しかし、もう少し複雑なシナリオが考えられます。

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)

出力:

Shape Name :Triangle
Sides :3
Hypotenuse :12

ここでhypotenuseを設定する前にsuper.initを呼び出していた場合、super.init呼び出しはprintShapeDescription()を呼び出していたことになり、それがオーバーライドされたため、最初はTriangleクラスのprintShapeDescription()の実装にフォールバックします。 TriangleクラスのprintShapeDescription()は、まだ初期化されていない非オプションのプロパティであるhypotenuseにアクセスします。 2フェーズ初期化では、プロパティ値が初期化される前にアクセスされるのを防ぎます

したがって、Twoフェーズの初期化が定義どおりに行われること、super.initを呼び出す順序を特定する必要があること、およびselfクラスによって導入されたすべてのプロパティを初期化した後でが必要になることを確認してください。安全チェック1

30
BangOperator

醜いフォーマットですみません。宣言の後に質問文字を入れるだけで、すべて問題ありません。質問は、値がオプションであることをコンパイラに伝えます。

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

編集1:

このエラーをスキップするより良い方法があります。 jmaschadのコメントによれば、あなたのケースではoptionalを使う理由はありません。optionalsは使い勝手がよくないので、アクセスする前に必ずoptionalがnilでないかどうかをチェックする必要があります。だからあなたがしなければならないのは宣言の後メンバーを初期化することだけです:

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

編集2 :

2分後にこの回答にたどり着いた後、私はさらに良い方法を見つけました。コンストラクタ内でクラスメンバを初期化したい場合は、コンストラクタ内でsuper.init()を呼び出す前に初期値を割り当てる必要があります。このような:

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

Swiftを学んで頑張ってください。

14
fnc12

Swiftは、すべてのメンバーvarが使用される前に初期化するように強制します。スーパーターンになるとどうなるのかわからないので、エラーになります。すみませんより安全です。

9
Daij-Djan

エドワード、

あなたの例のコードはこのように修正することができます:

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

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

これは暗黙的にラップされていないオプションを使用しています。

ドキュメンテーションで私たちは読むことができます:

「オプションと同様に、暗黙的にラップされていないオプションの変数またはプロパティを宣言するときに初期値を指定しないと、自動的にデフォルトのnilになります。

7
Pavel Gubarev

Swiftでは、Obj Cとは逆に、プロパティを初期化せずにスーパークラスを初期化することはできません。そのため、「super.init」を呼び出す前にすべてのプロパティを初期化する必要があります。

http://blog.scottlogic.com/2014/11/20/Swift-initialisation.html にアクセスしてください。それはあなたの問題にいい説明を与えます。

6
jishnu bala

宣言の末尾にnilを追加してください。


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

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

これは私の場合はうまくいきましたが、あなたの場合はうまくいかないかもしれません

5
Michael

あなたはただ間違った順番で始めています。

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