web-dev-qa-db-ja.com

なぜSwiftイニシャライザーはスーパークラスで便利なイニシャライザーを呼び出すことができないのですか?

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

これが許可されない理由はわかりません。最終的に、各クラスの指定されたイニシャライザは必要な値で呼び出されるので、なぜBのデフォルト値を再度指定してinitxで繰り返す必要があるのかinitの便利なAはうまくいくでしょうか?

86
Robert

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

ルール1:Designated初期化子は、直接のスーパークラスからdesignated初期化子を呼び出す必要があります。

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

強調鉱山。指定された初期化子は、便利な初期化子を呼び出すことはできません。

どのイニシャライザの「方向」が許可されるかを示すための規則に沿った図があります。

Initializer Chaining

25
Craig Otis

検討する

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:の再帰呼び出しになります。 )。

21
beba

それは、無限の再帰で終わる可能性があるからです。考慮してください:

_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()を呼び出します。

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

13
Julien

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

あなたがしなければならないのは、サブクラスのinitの中にある便利なinitを使用して、スーパークラスのインスタンスを作成することだけです。次に、作成したインスタンスを使用して、スーパーの指定された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
bigelerow

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

1
evanchin

初期化子とその継承の詳細な説明については、18:30のWWDCビデオ「403 Intermediate Swift」を参照してください。私が理解したように、次のことを考慮してください。

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)
    }
}
0
Ralf