Shape
とSquare
という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
を設定しなければならないのはなぜですか?
あなたの質問に答えるSwift Programming Languageからの引用:
「Swiftのコンパイラは、2段階の初期化がエラーなしで完了したことを確認するために4つの有用な安全チェックを実行します。」
安全性チェック1「指定された初期化子は、スーパークラス初期化子に委任する前に、そのクラスによって導入されたすべてのプロパティが初期化されていることを確認する必要があります」
抜粋:アップル社の "The Swift Programming Language"。 https://iTunes.Apple.com/us/book/Swift-programming-language/id881256329?mt=11
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では違います。メソッドの呼び出し(スーパークラスの初期化子を含む)を含む他のことをする前に、インスタンス変数が一貫した状態にあることを確認する責任があります。
すべてのインスタンス変数を初期化した後に "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
}
}
から 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
醜いフォーマットですみません。宣言の後に質問文字を入れるだけで、すべて問題ありません。質問は、値がオプションであることをコンパイラに伝えます。
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を学んで頑張ってください。
Swiftは、すべてのメンバーvarが使用される前に初期化するように強制します。スーパーターンになるとどうなるのかわからないので、エラーになります。すみませんより安全です。
エドワード、
あなたの例のコードはこのように修正することができます:
var playerShip:PlayerShip!
var deltaPoint = CGPointZero
init(size: CGSize)
{
super.init(size: size)
playerLayerNode.addChild(playerShip)
}
これは暗黙的にラップされていないオプションを使用しています。
ドキュメンテーションで私たちは読むことができます:
「オプションと同様に、暗黙的にラップされていないオプションの変数またはプロパティを宣言するときに初期値を指定しないと、自動的にデフォルトのnilになります。
Swiftでは、Obj Cとは逆に、プロパティを初期化せずにスーパークラスを初期化することはできません。そのため、「super.init」を呼び出す前にすべてのプロパティを初期化する必要があります。
http://blog.scottlogic.com/2014/11/20/Swift-initialisation.html にアクセスしてください。それはあなたの問題にいい説明を与えます。
宣言の末尾にnilを追加してください。
// Must be nil or Swift complains
var someProtocol:SomeProtocol? = nil
// Init the view
override init(frame: CGRect)
super.init(frame: frame)
...
これは私の場合はうまくいきましたが、あなたの場合はうまくいかないかもしれません
あなたはただ間違った順番で始めています。
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
}
}