webentwicklung-frage-antwort-db.com.de

Warum können Swift-Initialisierer keine Komfort-Initialisierer für ihre Oberklasse aufrufen?

Betrachten Sie die zwei Klassen:

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

Ich verstehe nicht, warum das nicht erlaubt ist. Letztendlich wird der festgelegte Initialisierer jeder Klasse mit allen erforderlichen Werten aufgerufen. Warum muss ich mich also in Bs init wiederholen, indem ich wieder einen Standardwert für x angibt, wenn die Bequemlichkeit init in A gut funktioniert?

81
Robert

Dies ist Regel 1 der "Initializer Chaining" -Regeln, wie im Swift-Programmierhandbuch angegeben, die wie folgt lautet:

Regel 1: Designated -Initialisierer müssen einen design -Initialisierer von ihrem .__ aufrufen. sofortige Superklasse.

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

Betonung meiner. Designierte Initialisierer können keine komfortablen Initialisierer aufrufen.

Es gibt ein Diagramm, das mit den Regeln übereinstimmt, um zu zeigen, welche "Anweisungen" für die Initialisierung zulässig sind:

Initializer Chaining

24
Craig Otis

Erwägen

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()

Da alle angegebenen Initialisierer der Klasse A überschrieben werden, erbt die Klasse B alle praktischen Initialisierer von A. Die Ausführung dieses Befehls wird ausgegeben

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

Wenn nun der festgelegte Initialisierer B.init (a: b :) den Bequemlichkeitsinitialisierer A.init (a :) der Basisklasse aufrufen darf, würde dies zu einem rekursiven Aufruf von B.init (a:, b: ).

20
beba

Es liegt daran, dass Sie mit einer unendlichen Rekursion enden können. Erwägen:

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

und schau mal:

let a = SubClass()

das wird SubClass.init() aufrufen, das SuperClass.init(value:) aufrufen wird, das SubClass.init() aufruft.

Die festgelegten/Convenience-Init-Regeln sind so ausgelegt, dass eine Klasseninitialisierung immer korrekt ist.

11
Julien

Ich habe dafür eine Arbeit gefunden. Es ist nicht sehr hübsch, aber es löst das Problem, die Werte einer Oberklasse nicht zu kennen oder Standardwerte festlegen zu wollen. 

Alles, was Sie tun müssen, ist eine Instanz der Superklasse mit der Bequemlichkeit init rechts in der init der Unterklasse zu erstellen. Dann rufen Sie die bezeichnete init des Supers mit der gerade erstellten Instanz auf.

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

Schauen Sie sich das WWDC-Video "403 Intermediate Swift" um 18:30 an, um eine ausführliche Erklärung der Initialisierer und ihrer Vererbung zu erhalten. Wenn ich es verstanden habe, bedenke folgendes:

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

Aber betrachten wir eine Wyrm-Unterklasse: Ein Wyrm ist ein Drache ohne Beine und Flügel. Der Initializer für einen Wyvern (2 Beine, 2 Flügel) ist also falsch! Dieser Fehler kann vermieden werden, wenn der Bequemlichkeits-Wyvern-Initializer einfach nicht aufgerufen werden kann, sondern nur der vollständig bestimmte Initializer:

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

Erwägen Sie, den Initialisierungscode aus Ihrer praktischen init() in eine neue Hilfsfunktion foo() zu extrahieren. Rufen Sie foo(...) auf, um die Initialisierung in Ihrer Unterklasse durchzuführen.

0
evanchin