57348


プレイヤーから放つ弾を作りたくて SKShapeNode
クラスを使って円を作りたいのですが、エラーが出てしまいます。読む限り、このクラス用に作られたものを指定しろと言われているようなのですが、使いたてでなにぶんわかりません。
とても短いですがこのようなコードになっています。

[code]

     import Foundation
     import UIKit
     import SpriteKit
    
    class Shooter: SKShapeNode {
        init() {
            super.init(circleOfRadius: 5)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
[/code]

そして、エラーの内容は

> Must call a designated initializer of the superclass 'SKShapeNode'

となっています。私は一体何を指定してあげれば良いのでしょうか、ご回答よろしくお願いします。


CC BY-SA 4.0
2019-08-13T12:16:30.913
57332
2019-08-14T02:55:11.800
2019-08-13T12:23:51.110
32986
16877
question
swift4 spritekit
SKShapeNodeクラスで円を描きたいが、initでエラー


Swiftで、自前でサブクラスを定義しようと思うとdesignated initializer(指名イニシャライザ)とconvenience
initializer(利便イニシャライザ)の概念は避けて通るわけにはいきません。出来るだけきちんとした記述をしている参考書籍などを探して、しっかり勉強してください。

(残念ながらSwiftで使われている用語には決定版の定訳と言うものがありませんので、括弧内の訳語は書籍やサイトでは別の訳語が当てられているかもしれません。)

ここでは[原典のSwift本](https://swift.org/documentation/#the-swift-programming-
language)から。

[Class Inheritance and Initialization](https://docs.swift.org/swift-
book/LanguageGuide/Initialization.html#ID216)

> ## Initializer Delegation for Class Types
>
> ...
>
> ### Rule 1
>
> A designated initializer must call a designated initializer from its
> immediate superclass.
>
> ### Rule 2
>
> A convenience initializer must call another initializer from the same class.
>
> ### Rule 3
>
> A convenience initializer must ultimately call a designated initializer.

(拙訳)

> ## クラス型の初期化委譲
>
> ...
>
> ### ルール1
>
> 指名イニシャライザでは、直接の親クラスの指名イニシャライザを呼ばなければいけない
>
> ### ルール2
>
> 利便イニシャライザでは、同クラスの他のイニシャライザを呼ばなければいけない
>
> ### ルール3
>
> 利便イニシャライザでは、最終的には指名イニシャライザを呼ばなければいけない

ご質問のコードの場合、`Shooter`クラスのイニシャライザ`init()`には`convenience`キーワードがないため、「指名イニシャライザを宣言している」ことになるので、その中で「直接の親クラスの指名イニシャライザを呼ばなければいけない」と言うルール1が適用されることになるのですが、[`init(circleOfRadius:)`](https://developer.apple.com/documentation/spritekit/skshapenode/1519570-init)は指名イニシャライザではないため、ルール違反でエラーになっているわけです。

* * *

対処として、指名イニシャライザを呼ぼうと思っても、[`SKShapeNode`クラス](https://developer.apple.com/documentation/spritekit/skshapenode)には殆ど指名イニシャライザが見つからないため、ルール1を満たすことはできない、と言うことになってしまいます。

ただ、あなたの今回のコードでしたら、ルール2を満たすことはできそうです。

[code]

    class Shooter: SKShapeNode {
        override convenience init() {
            self.init(circleOfRadius: 5)
        }
    }
    
[/code]

`init()`を利便イニシャライザにして、継承した`init(circleOfRadius:)`を呼んでやることで「同クラスの他のイニシャライザを呼ばなければいけない」と言うルールが守れていることになります。

* * *

上記のようなサブクラス化を可能とするためには、「どんな条件でどれだけの範囲のイニシャライザが継承されるか」のルールも、よく知っておかないといけないのですが、「指名イニシャライザを一つも定義しないと指名イニシャライザも利便イニシャライザも全部継承される」と言うルールがあるため、今回はなんとかなりますが、もう少しあれこれコードを付け足すと、うまくいかない場合も出てくるかもしれません。

Swiftのこの辺りのルールは、よく使われているオブジェクト指向言語の中でも、かなり厳しくわかりにくい方に入るように思います。なにかよくわからない点、うまくいかない点があれば再度ご質問ください。


CC BY-SA 4.0
2019-08-13T16:58:48.850
57334
2019-08-13T16:58:48.850
13972
57332
answer


別回答者さんから、クラスの継承についてと、イニシアライザに関する解説がありましたが、ここでは実践的に「じゃあどう書き直せばいいの？」な話をしたいと思います。

まず、サブクラス`Shooter`は、親クラスの指定イニシアライザ（Designated
Initializer。別回答者さんは「指名イニシアライザ」と訳しています）を上書き（とくに意図がない場合は、同時に継承（Delegate）も）します。イニシアライザには、指定イニシアライザと簡便イニシアライザ（Convenience
Initializer。別回答者さんの訳では「利便イニシアライザ」）の2種類がありますが、[リファレンス](https://developer.apple.com/documentation/spritekit/skshapenode)上では、指示子がないのが指定イニシアライザ、`convenience`指示子があるのが、簡便イニシアライザです。  
`SKShapeNode`のリファレンスには、簡便イニシアライザしかありませんが、親クラスの`SKNode`にある指定イニシアライザが、そのまま`SKShapeNpde`の指定イニシアライザとなります。`init()`と`init?(coder
aDecoder: NSCoder)`の二つがあります。

[code]

    class Shooter: SKShapeNode {
    
        override init() {
            super.init() // 親クラスから継承
        }
    
        // とりあえず、Xcodeの「改善提案」で入力されるコードをそのまま使っています。
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    }
    
[/code]

さて、`init()`は指定イニシアライザとして使ってしまいましたから、希望である簡便イニシアライザとしては、使えなくなってしまいました。少し妥協をして、塗りつぶし色を指定して初期化するイニシアライザを考えることにしましょう。

[code]

    convenience init(fillColor: SKColor) {
        self.init(circleOfRadius: 5)
        self.fillColor = fillColor
        self.strokeColor = .clear
    }
    
[/code]

これで、`let shooter = Shooter(fillColor: .red)`というコードで、新しいノードを作ることができます。

`Shooter()`というコードで新しいノードを作ることはあきらめたくないという場合は、指定イニシアライザ`init()`を意図通りに実装することを考えます。

[code]

    class Shooter: SKShapeNode {
        override init() {
            super.init()
            self.path = CGPath(ellipseIn: CGRect(x: -5, y: -5, width: 10, height: 10), transform: nil)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    }
    
[/code]

`init(circleOfRadius: CGFloat)`を使わずに、地道にプロパティ`path`を編集して、円を描きます。


CC BY-SA 4.0
2019-08-14T02:55:11.800
57348
2019-08-14T02:55:11.800
18540
57332
answer