学習課題として、数値を受け取り、数値の幅と高さの2乗であるShapeNodeを構築する新しい便利なイニシャライザを提供するSKShapeNode
のサブクラスを実装しようとしています。
Swift Book によると:
ルール1
サブクラスが指定された初期化子を定義していない場合、サブクラスは、スーパークラスの指定された初期化子をすべて自動的に継承します。
ルール2
サブクラスがすべてのスーパークラス指定イニシャライザの実装を提供する場合(ルール1に従って継承するか、その定義の一部としてカスタム実装を提供することにより)、スーパークラスの便利な初期化子すべてを自動的に継承します。」
ただし、次のクラスは機能しません。
_class MyShapeNode : SKShapeNode {
convenience init(squareOfSize value: CGFloat) {
self.init(rectOfSize: CGSizeMake(value, value))
}
}
_
代わりに私は得る:
_Playground execution failed: error: <REPL>:34:9: error: use of 'self' in delegating initializer before self.init is called
self.init(rectOfSize: CGSizeMake(value, value))
^
<REPL>:34:14: error: use of 'self' in delegating initializer before self.init is called
self.init(rectOfSize: CGSizeMake(value, value))
^
<REPL>:35:5: error: self.init isn't called on all paths in delegating initializer
}
_
私の理解では、MyShapeNode
はSKShapeNode
の便利なイニシャライザをすべて継承する必要があります。これは、自分で指定したイニシャライザを実装していないため、また、便利なイニシャライザが別のinit(rectOfSize)
を呼び出すためです。便利な初期化子、これは動作するはずです。何が悪いのですか?
イニシャライザの継承についての私の理解はあなたのものと同じであり、私たちはどちらもこの本の内容によく合っていると思います。私はそれが解釈の問題だとか、規定されたルールの誤解だとは思わない。とはいえ、あなたが何か悪いことをしているとは思いません。
私はプレイグラウンドで以下をテストしましたが、期待通りに機能します:
class RectShape: NSObject {
var size = CGSize(width: 0, height: 0)
convenience init(rectOfSize size: CGSize) {
self.init()
self.size = size
}
}
class SquareShape: RectShape {
convenience init(squareOfSize size: CGFloat) {
self.init(rectOfSize: CGSize(width: size, height: size))
}
}
RectShape
はNSObject
を継承し、指定された初期化子を定義しません。したがって、ルール1に従って、NSObject
の指定された初期化子をすべて継承します。実装で提供した便利な初期化子は、インスタンスのセットアップを行う前に、指定された初期化子に正しく委任します。
SquareShape
はRectShape
を継承し、指定された初期化子を提供しません。また、ルール1に従って、SquareShape
の指定された初期化子をすべて継承します。ルール2に従って、RectShape
で定義された簡易イニシャライザも継承します。最後に、SquareShape
で定義されたコンビニエンス初期化子は、継承されたコンビニエンス初期化子に適切に委譲し、次に、継承された指定イニシャライザに委譲します。
したがって、あなたが何も悪いことをしておらず、私の例が期待どおりに機能するという事実を踏まえて、私は次の仮説を推定しています。
SKShapeNode
はObjective-Cで記述されているため、「すべての便利なイニシャライザは同じクラスから別のイニシャライザを呼び出さなければならない」というルールは、言語によって強制されません。したがって、SKShapeNode
の便利なイニシャライザは、指定されたイニシャライザを実際には呼び出しません。そのため、サブクラスMyShapeNode
が便利な初期化子を期待どおりに継承しても、継承された指定された初期化子に適切に委任されません。
しかし、これも仮説に過ぎません。私が確認できるのは、私が作成した2つのクラスでメカニズムが期待どおりに機能することだけです。
ここには2つの問題があります。
SKShapeNodeには、指定された初期化子init()
が1つだけあります。つまり、init()
を呼び出さないと、イニシャライザから抜け出すことができません。
SKShapeNodeには、_CGPath!
_として宣言されたpath
プロパティがあります。これは、path
を何らかの方法で初期化せずにイニシャライザから抜け出したくないことを意味します。
これら2つのことの組み合わせが問題の原因です。簡単に言えば、SKShapeNodeは正しく記述されていません。初期化する必要があるpath
プロパティがあります。したがって、path
を設定する指定された初期化子が必要です。しかし、そうではありません(そのpath
- setting初期化子はすべて、便利な初期化子です)。それはバグです。別の言い方をすれば、問題の原因は、便利かどうかにかかわらず、_shapeNodeWith...
_メソッドは実際には初期化子ではないということです。
それでも、他のイニシャライザを強制的に作成することなく、便利なイニシャライザを作成できます両方の要件の順序でを満たすことにより、つまり次のように記述します。
_class MyShapeNode : SKShapeNode {
convenience init(squareOfSize value: CGFloat) {
self.init()
self.init(rectOfSize: CGSizeMake(value, value))
}
}
_
違法に見えますが違法ではありません。 self.init()
を呼び出すと、最初の要件が満たされ、self
を自由に参照できるようになりました(self.initが「エラー」と呼ばれ、2番目の要件を満たします。
マットの答えに基づいて、追加の関数を含める必要がありました。そうしないと、コンパイラーは引数なしで初期化子を呼び出すことについて不平を言っていました。
以下は、SKShapeNodeをサブクラス化するために機能したものです。
class CircleNode : SKShapeNode {
override init() {
super.init()
}
convenience init(width: CGFloat, point: CGPoint) {
self.init()
self.init(circleOfRadius: width/2)
// Do stuff
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2019年からの朗報です。次の3つの初期化子を持つSKShapeサブクラスができたと報告できます。
override init() {
super.init()
}
convenience init(width: CGFloat, point: CGPoint) {
self.init(circleOfRadius: width/2)
self.fillColor = .green
self.position = point
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
期待どおりに動作します。コンビニエンス初期化子を呼び出すと、目的の位置に緑色のドットが表示されます。 (@mattと@Crashalotで記述されているinit()の二重呼び出しは、エラーになります)。
.sksシーンエディターでSKShapeNodesを変更できるようにしたいのですが、すべてを使用できるわけではありません。まだ。