web-dev-qa-db-fra.com

Pourquoi les initialiseurs Swift ne peuvent-ils pas appeler des initialiseurs pratiques dans leur super-classe?

Considérons les deux classes:

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

Je ne vois pas pourquoi cela n'est pas autorisé. En fin de compte, l'initialiseur désigné de chaque classe est appelé avec toutes les valeurs dont il a besoin, alors pourquoi dois-je me répéter dans la variable B en init en spécifiant à nouveau une valeur par défaut pour x, lorsque la commodité init dans A va de nouveau?

81
Robert

Voici la règle 1 des règles "Chaînage d'initialisation" spécifiées dans le Guide de programmation Swift, qui se lit comme suit:

Règle 1: Les initialiseurs désignés doivent appeler un initialiseur désigné à partir de leur superclasse immédiate.

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

L'accent est à moi. Les initialiseurs désignés ne peuvent pas appeler les initialiseurs de commodité.

Un diagramme accompagne les règles pour montrer quelles "instructions" d'initialisation sont autorisées:

Initializer Chaining

24
Craig Otis

Considérer

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

Étant donné que tous les initialiseurs désignés de la classe A sont remplacés, la classe B héritera de tous les initialiseurs de commodité de A. L'exécution de cette opération générera

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

Maintenant, si l’initialiseur désigné B.init (a: b :) est autorisé à appeler l’initialisateur de classe de base A.init (a :), un appel récursif à B.init (a:, b: ).

20
beba

C'est parce que vous pouvez vous retrouver avec une récursion infinie. Considérer:

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

et regardez:

let a = SubClass()

qui appelle SubClass.init() qui appelle SuperClass.init(value:) qui appelle SubClass.init().

Les règles init désignées/de commodité sont conçues pour qu'une initialisation de classe soit toujours correcte.

11
Julien

J'ai trouvé un travail pour ça. Ce n'est pas très beau, mais cela résout le problème de ne pas connaître les valeurs d'une super-classe ou de vouloir définir des valeurs par défaut. 

Tout ce que vous avez à faire est de créer une instance de la superclasse, en utilisant la commodité init, directement dans la init de la sous-classe. Ensuite, vous appelez la init désignée du super en utilisant l'instance que vous venez de créer.

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

Regardez la vidéo WWDC "403 intermediate Swift" à 18h30 pour une explication détaillée des initialiseurs et de leur héritage. Si j'ai bien compris, considérez ce qui suit:

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

Mais considérons maintenant une sous-classe de Wyrm: Un Wyrm est un dragon sans jambes ni ailes. Donc, l'initialiseur pour un Wyvern (2 jambes, 2 ailes) est mauvais pour cela! Cette erreur peut être évitée si la commodité de Wyvern-Initializer ne peut tout simplement pas être appelée, mais uniquement l'initialiseur désigné complet:

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

Pensez à extraire le code d'initialisation de votre pratique init() dans une nouvelle fonction d'assistance foo(), appelez foo(...) pour effectuer l'initialisation dans votre sous-classe.

0
evanchin