Swift初期化子がスーパークラスのコンビニエンス初期化子を呼び出せないのはなぜですか?


88

次の2つのクラスを考えます。

class A {
    var x: Int

    init(x: Int) {
        self.x = x
    }

    convenience init() {
        self.init(x: 0)
    }
}

class B: A {
    init() {
        super.init() // Error: Must call a designated initializer of the superclass 'A'
    }
}

これが許可されない理由がわかりません。最終的には、各クラスの指定イニシャライザは、なぜ私は自分自身を繰り返す必要がない、彼らが必要とする任意の値で呼び出されたBS」initのデフォルト値を指定することでx利便とき、再びinitではAうまくやるだろうか?


2
答えを探しましたが、満足できる答えは見つかりませんでした。これはおそらく実装上の理由です。別のクラスで指定されたイニシャライザを検索する方が、便利なイニシャライザを検索するよりもはるかに簡単かもしれません。
Sulthan

@ロバート、以下のコメントをありがとう。質問にそれらを追加したり、受け取った内容を記載した回答を投稿したりできると思います。「これは設計によるものであり、関連するバグはこの領域で分類されています。」そのため、理由を説明できない、または説明したくないようです。
フェランメイリンチ

回答:


24

これは、「Swiftプログラミングガイド」で指定されている「初期化チェーン」ルールのルール1です。

ルール1:指定イニシャライザは、直接のスーパークラスから指定イニシャライザを呼び出す必要があります。

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html

強調鉱山。指定イニシャライザは、便利なイニシャライザを呼び出すことはできません。

イニシャライザの「方向」が許可されていることを示すためのルールに沿った図があります。

初期化チェーン


80
しかし、なぜそれがこのように行われるのですか?ドキュメントには、設計を簡略化すると書かれているだけですが、指定されたイニシャライザにデフォルト値を継続的に指定して繰り返し続ける必要がある場合、これがどのように当てはまるのかわかりません。初期化子はしますか?
Robert

5
私はこの質問の数日後にバグ17266917を提出し、便利なイニシャライザに電話をかけられるように求めました。まだ返答はありませんが、他のSwiftバグレポートについてもまったく知りませんでした。
ロバート

8
うまくいけば、彼らは便利なイニシャライザを呼び出すことができます。SDKの一部のクラスでは、特定の動作を実現する他の方法はありませんが、便利な初期化子を呼び出します。参照SCNGeometry:を追加できるSCNGeometryElementのは、便利なイニシャライザのみなので、継承できません。
2014

4
これは非常に貧弱な言語設計の決定です。新しいアプリの最初から、迅速に開発することに決めました。サブクラスからNSWindowController.init(windowNibName)を呼び出す必要がありますが、それはできません:(
Uniqus

4
@Kaiserludi:有用なものは何もありませんでした。「これは設計によるものであり、関連するバグはこの領域で分類されています。」
Robert

21

検討する

class A
{
    var a: Int
    var b: Int

    init (a: Int, b: Int) {
        print("Entering A.init(a,b)")
        self.a = a; self.b = b
    }

    convenience init(a: Int) {
        print("Entering A.init(a)")
        self.init(a: a, b: 0)
    }

    convenience init() {
        print("Entering A.init()")
        self.init(a:0)
    }
}


class B : A
{
    var c: Int

    override init(a: Int, b: Int)
    {
        print("Entering B.init(a,b)")
        self.c = 0; super.init(a: a, b: b)
    }
}

var b = B()

クラスAのすべての指定された初期化子がオーバーライドされるため、クラスBはAのすべての便利な初期化子を継承します。これを実行すると、

Entering A.init()
Entering A.init(a:)
Entering B.init(a:,b:)
Entering A.init(a:,b:)

ここで、指定されたイニシャライザB.init(a:b :)が基本クラスの便利なイニシャライザA.init(a :)の呼び出しを許可される場合、これはB.init(a:、b:の再帰呼び出しになります。 )。


これは簡単に回避できます。指定された初期化子内からスーパークラスの簡易イニシャライザを呼び出すとすぐに、スーパークラスの簡易イニシャライザは継承されなくなります。
fluidsonic 2015

@fluidsonicですが、構造とクラスのメソッドは、それらがどのように使用されているかによって変化するため、異常です。デバッグの楽しみを想像してください!
kdazzle 16

2
@kdazzleクラスの構造もクラスメソッドも変化しません。なぜ彼らは?-考えられる唯一の問題は、コンビニエンスイニシャライザは継承時にサブクラスの指定イニシャライザに動的に委任する必要があり、継承されていないがサブクラスから委任されている場合は、独自のクラスの指定イニシャライザに静的に委任する必要があるということです。
fluidsonic

通常の方法でも同様の再帰問題が発生する可能性があると思います。それは、メソッドを呼び出すときの決定に依存します。たとえば、無限ループに陥る可能性があるために、言語が再帰呼び出しを許可しない場合は、ばかげたことになります。プログラマは自分が何をしているかを理解する必要があります。:)
フェランメイリンチ

13

無限の再帰が発生する可能性があるためです。検討してください:

class SuperClass {
    init() {
    }

    convenience init(value: Int) {
        // calls init() of the current class
        // so init() for SubClass if the instance
        // is a SubClass
        self.init()
    }
}

class SubClass : SuperClass {
    override init() {
        super.init(value: 10)
    }
}

そして見てください:

let a = SubClass()

これを呼び出すSubClass.init()呼び出しますどのSuperClass.init(value:)呼んであろうSubClass.init()

指定された/便利な初期化ルールは、クラスの初期化が常に正しいように設計されています。


1
サブクラスは、そのスーパークラスからコンビニエンス初期化子を明示的に呼び出さない場合がありますが、サブクラスが指定されたすべてのスーパークラスの初期化子(たとえば、この例)の実装を提供する場合、それらを継承できます。したがって、上記の例は実際に当てはまりますが、スーパークラスのコンビニエンス初期化子に明示的に関連付けられていない特別なケースですが、指定されたイニシャライザがコンビニエンス初期化子を呼び出さない可能性があります。これは、上記のような再帰的なシナリオにつながるためです。 。
dfri 2017年

1

これの回避策を見つけました。とてもきれいではありませんが、スーパークラスの値がわからない、またはデフォルト値を設定したいという問題を解決します。

必要なinitinitは、サブクラスので、便利なを使用してスーパークラスのインスタンスを作成することだけです。次に、init作成したインスタンスを使用してスーパーの指定を呼び出します。

class A {
    var x: Int

    init(x: Int) {
        self.x = x
    }

    convenience init() {
        self.init(x: 0)
    }
}

class B: A {
    init() {
        // calls A's convenience init, gets instance of A with default x value
        let intermediate = A() 

        super.init(x: intermediate.x) 
    }
}

1

初期化コードを便利なものinit()から新しいヘルパー関数foo()に抽出することを検討foo(...)してください。サブクラスで初期化を行うにはを呼び出します。


良い提案ですが、実際には質問の答えにはなりません。
SwiftsNamesake

0

イニシャライザとその継承の詳細な説明については、18:30にWWDCビデオ「403中間スウィフト」をご覧ください。私が理解しているように、次のことを考慮してください。

class Dragon {
    var legs: Int
    var isFlying: Bool

    init(legs: Int, isFlying: Bool) {
        self.legs = legs
        self.isFlying = isFlying
    }

    convenience initWyvern() { 
        self.init(legs: 2, isFlying: true)
    }
}

しかし、今度はWyrmサブクラスについて考えてみましょう。Wyrmは、脚も翼もないドラゴンです。したがって、ワイバーンの初期化子(2脚、2翼)は間違っています。このエラーは、便利なWyvern-Initializerを単に呼び出せず、指定された完全なInitializerのみを呼び出すことができる場合に回避できます。

class Wyrm: Dragon {
    init() {
        super.init(legs: 0, isFlying: false)
    }
}

12
それは本当に理由ではありません。initWyvern呼び出すのが理にかなっているときにサブクラスを作成するとどうなりますか?
スルタン

4
ええ、私は確信していません。Wyrm便利なイニシャライザを呼び出した後、レッグの数をオーバーライドするのを止めるものは何もありません。
Robert

これは、WWDCビデオで示されている理由です(車->レースカーおよびブール型hasTurboプロパティのみ)。サブクラスが指定されたすべての初期化子を実装している場合は、それよりも便利な初期化子を継承します。そこには意味があり、Objective-Cの動作にも正直に問題はありませんでした。最初にObjective-Cのようにではなく、initの最後にsuper.initを呼び出す新しい規則も参照してください。
ラルフ

IMOのsuper.initを「最後」と呼ぶ規約は概念的には新しいものではありません。objective-cと同じですが、そこには、すべてに自動的に初期値(nil、0など)が割り当てられていました。Swiftでは、このフェーズを初期化する必要があります。このアプローチの利点は、別の初期値を割り当てるオプションがあることです。
Roshan 14

-1

なぜ2つのイニシャライザ(デフォルト値を持つもの)がないのですか?

class A {
  var x: Int

  init(x: Int) {
    self.x = x
  }

  init() {
    self.x = 0
  }
}

class B: A {
  override init() {
    super.init()

    // Do something else
  }
}

let s = B()
s.x // 0
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.