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


218

私には2つのクラスがShapeあり、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
    }
}

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

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

self.sideLength呼び出す前に設定する必要があるのはなぜsuper.initですか?


これは、実際の技術的な制限ではなく、適切なプログラミング方法と関係があることを確認してください。ShapeがSquareがオーバーライドした関数を呼び出す場合、SquareはsideLengthを使用する可能性がありますが、まだ初期化されていません。Swiftはおそらく、基本クラスを呼び出す前にまずインスタンスを初期化することを強制することにより、偶然これを行うことを制限します。
cwharris 2014年

3
本の例は悪いです。以下で説明するように、すべてのプロパティが初期化されていることを確認するために、常に最後super.init()を呼び出す必要があります。
Pascal

回答:


173

あなたの質問に答えるSwiftプログラミング言語からの引用:

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

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

抜粋:Apple Inc.「The Swift Programming Language」iBooks。 https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11


47
これは、C ++、C#、またはJavaと比較して驚異的な変更です。
MDJ 2014年

12
@MDJ確かに。正直なところ、その付加価値もわかりません。
Ruben

20
実はあるようです。C#では、スーパークラスコンストラクターはオーバーライド可能な(仮想)メソッドを呼び出すべきではありません。なぜなら、完全に初期化されていないサブクラスに対してどのように反応するかは誰にもわからないからです。Swiftでは問題ありません。スーパークラスコンストラクターが実行されているときは、サブクラスの追加状態が問題ないからです。さらに、Swiftではfinalメソッドを除くすべてがオーバーライド可能です。
MDJ 2014年

17
独自のサブビューを作成するUIViewサブクラスを作成したい場合は、最初にフレームなしでこれらのサブビューを初期化し、後でフレームを追加する必要があるため、ビューの参照を参照できないため、特にそれが厄介だと感じています。 super.initを呼び出した後までバインドします。
Ash

5
@Janosプロパティをオプションにした場合、で初期化する必要はありませんinit
JeremyP 2015年

105

Swiftには、イニシャライザで実行される非常に明確で特定の操作シーケンスがあります。いくつかの基本的な例から始めて、一般的なケースに至るまで作業を進めていきましょう。

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

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

Aにはスーパークラスがないので、super.init()関数が存在しないため呼び出すことができないことに注意してください。

それでは、Bという名前の新しいクラスでAをサブクラス化しましょう。

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

これは、[super init]通常最初に呼び出されるObjective-Cからの逸脱です。Swiftではそうではありません。メソッドの呼び出し(スーパークラスのイニシャライザを含む)など、他のことを行う前に、インスタンス変数が一貫した状態にあることを確認する必要があります。


1
これは非常に役立つ、明確な例です。ありがとうございました!
FullMetalFist

たとえば、yを計算するために値が必要な場合はどうなりますか。init(y:Int){self.y = y * self.x super.init()}
6rod9

1
init(y:Int、x:Int = 0)のようなものを使用します{self.y = y * x; self.x = x; super.init(x:x)}、また、スーパークラス名Aには空のコンストラクタがないため、スーパークラスの空のコンストラクタを直接呼び出すことはできません
Hitendra Solanki

43

ドキュメントから

安全チェック1

指定された初期化子は、スーパークラスの初期化子に委譲する前に、そのクラスによって導入されたすべてのプロパティが初期化されていることを確認する必要があります。


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

これに答えるために、初期化プロセスを迅速に行ってみましょう。

2フェーズ初期化

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

2フェーズの初期化プロセスを使用すると、初期化が安全になり、クラス階層内の各クラスに完全な柔軟性が提供されます。2フェーズの初期化により、プロパティ値が初期化される前にアクセスされたり、別のイニシャライザによって予期せずにプロパティ値が別の値に設定されたりすることが防止されます。

したがって、2つのステップの初期化プロセスが上記のように実行されたことを確認するために、4つの安全性チェックがあり、そのうちの1つは、

安全チェック1

指定された初期化子は、スーパークラスの初期化子に委譲する前に、そのクラスによって導入されたすべてのプロパティが初期化されていることを確認する必要があります。

現在、2フェーズの初期化では順序について話しませんが、この安全性チェックでsuper.initは、すべてのプロパティの初期化後に順序付けが行われます。

安全チェック1は、2段階の初期化により、プロパティ値が初期化される前にアクセスできなくなるため、この安全チェック1がなくても満たすことができるため、無関係であると思わ れる場合があります

このサンプルのように

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

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

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


1
素晴らしい説明、なぜトップの回答に間違いなく追加すべきなの
Guy Daher

つまり、スーパークラスinit (オーバーライドされた)関数を呼び出す可能性があるため、基本的にはその関数がサブクラスプロパティにアクセスするため、super値が設定されないようにするには、すべての値が設定された後にへの呼び出しを行う必要があります。わかりました。Objective-Cはそれをどのようにして行い、なぜsuper最初に電話をかけなければならなかったのかと思いますか?
ハニー

基本的に何をする指摘していることである同様の配置:にprintShapeDescription() する前に self.sides = sides; self.name = named;このエラーが発生することになります:use of 'self' in method call 'printShapeDescription' before all stored properties are initialized。OPのエラーは、実行時エラーの「可能性」を減らすために提供されます。
ハニー

私が特に「可能性」という言葉を使用したのprintShapeDescriptionは、selfそれが参照しない関数、つまり「print( "nothing")」のようなものである場合、問題はなかったからです。(それでも、それはスマートではないため、コンパイラはエラーをスローします)
ハニー

まあobjcは安全ではありませんでした。Swiftはタイプセーフなので、オプションではないオブジェクトは本当にnilである必要があります!
Daij-Djan 2017

36

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

Appleの「Intermediate Swift」ビデオ(Apple Developerビデオリソースページhttps://developer.apple.com/videos/wwdc/2014/で見つけることができます)の約28:40に、すべてのイニシャライザがインスタンス変数を初期化した後でスーパークラスを呼び出す必要があります。

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

1
想像もしなかったでしょう。superでの初期化は最初のステートメントである必要があると私は思ったでしょう。!! うーん。かなりの変化で迅速に。
mythicalcoder

なぜこれが後に続く必要があるのですか?技術的な理由を入力してください
Masih

14

醜いフォーマットでごめんなさい。宣言の後に質問文字を置くだけで、すべてがOKになります。質問は、値がオプションであることをコンパイラーに伝えます。

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:

このエラーをスキップするより良い方法があります。jmaschadのコメントによると、あなたの場合にオプションを使用する理由はありません。オプションは使いにくいので、アクセスする前にオプションが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
    }
}

Edit2:

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の学習に頑張ってください。


ただ切り替えるsuper.init(name:name)self.sideLength = sideLengthsideLengthオプションとして宣言することは誤解を招き、後で強制的にアンラップを解除しなければならないときに、追加の手間がかかります。
ヨハネスルオン2014

ええ、これはオプションです。ありがとう
fnc12

実際には、だけにすることができます。var sideLength: Double初期値を割り当てる必要はありません
Jarsen

オプションの定数が本当にある場合はどうなりますか?どうすればよいですか?コンストラクタで初期化する必要がありますか?なぜそうしなければならないのかはわかりませんが、コンパイラはSwift 1.2で不平を言っています
Van Du Tran

1
パーフェクト!3つのソリューションはすべて「?」、「String()」で機能しましたが、問題の1つは、プロパティの1つを「割り当て」ていなかったためです。ありがとうメイト
Naishta

9

swiftは、すべてのメンバー変数を使用する前に初期化することを強制します。スーパーターンになるとどうなるかわからないため、エラーになります。申し訳ありませんが安全です


1
親クラスはその子で宣言されたプロパティを表示できないため、これはIMOには意味がありません。
アンディヒン

1
それはしかし、あなたはものをオーバーライドして、「スーパーはそれを作った前に」自己を使用して開始することができていません
Daij-Djan

こことそれに続くコメントを見ることができますか?私はあなたが言っていることを正確に言っていると思います。つまり、どちらもコンパイラーが残念ではなく安全になりたがっているということです。私の唯一の質問は、objective-cがこの問題をどのように解決したのでしょうか。それともしませんでしたか?そうでない場合は、なぜsuper.init最初の行に書き込む必要があるのですか?
ハニー

7

エドワード、

この例のコードを次のように変更できます。

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

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

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

ドキュメントで読むことができます:

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


これは最もクリーンなオプションだと思います。最初のSwiftの試みでは、AVCaptureDevice型のメンバー変数があり、直接インスタンス化できないため、init()コードが必要でした。ただし、ViewControllerには複数のイニシャライザが必要であり、共通の初期化メソッドをinit()から呼び出すことができないため、この答えは、すべてのイニシャライザで重複したコードのコピー/貼り付けを回避する唯一のオプションのようです。
sunetos 2014年


6

宣言の最後にnilを追加します。


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

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

これは私のケースではうまくいきましたが、あなたのケースではうまくいかないかもしれません


良い方法、プロトコル付きのUIViewControllerでUIViewを使用しているとき
abdul sathar

1

間違った順序で初期化しているだけです。

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

0

私はおそらくいくつかの反対票を受け取るでしょうが、正直に言うと、人生はこのように簡単です:

class CSListServerData<ListItem: CSJsonData>: CSServerData {
    var key: String!
    var type: ListItem.Type!
    var property: CSJsonDataList<ListItem>!

    func construct(_ key: String, _ type: ListItem.Type) -> Self {
        self.key = key
        self.type = type
        property = CSJsonDataList(self, type, key)
        return self
    }

    func construct(_ type: ListItem.Type) { construct("list", type) }

    var list: [ListItem] { property.list }
}

-4

それは驚くほど愚かなデザインです。

このようなものを考えてみましょう:

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

上記のように、これは無効です。しかしそうです:

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

「自己」が初期化されていないからです。

このバグがすぐに修正されることを願っています。

(はい、私は空のオブジェクトを作成してからサイズを設定できることを知っていますが、それはただ愚かです)。


2
これはバグではなく、OOPの新しいプログラミングの基本です。
Hitendra Solanki 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.