Pregunta ¿Por qué los inicializadores Swift no pueden llamar a los inicializadores convenientes en su superclase?


Considera las dos clases:

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

No veo por qué esto no está permitido. En última instancia, se llama al inicializador designado de cada clase con cualquier valor que necesiten, entonces, ¿por qué necesito repetirme en Bes init especificando un valor predeterminado para x nuevamente, cuando la conveniencia init en A va a hacer bien?


74
2018-06-09 14:27


origen


Respuestas:


Esta es la Regla 1 de las reglas de "Encadenamiento del inicializador" tal como se especifica en la Guía de programación Swift, que dice:

Regla 1: Designada los inicializadores deben llamar a un designada inicializador de su   superclase inmediata.

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

Énfasis mío Los inicializadores designados no pueden llamar a los inicializadores de conveniencia.

Hay un diagrama que va junto con las reglas para demostrar qué "indicaciones" de inicialización están permitidas:

Initializer Chaining


23
2018-06-09 14:35



Considerar

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

Debido a que todos los inicializadores designados de la clase A son anulados, la clase B heredará todos los inicializadores de conveniencia de A. Así que ejecutar esto dará como resultado

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

Ahora, si el inicializador designado B.init (a: b :) podría llamar al inicializador de conveniencia de clase base A.init (a :), esto daría como resultado una llamada recursiva a B.init (a:, b: )


17
2017-09-21 14:28



Es porque puedes terminar con una recursión infinita. Considerar:

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

y mira:

let a = SubClass()

que llamará SubClass.init() que llamará SuperClass.init(value:) que llamará SubClass.init().

Las reglas de inicio designadas / de conveniencia están diseñadas para que la inicialización de una clase siempre sea correcta.


9
2018-01-11 18:20



Encontré un trabajo alrededor para esto. No es súper bonito, pero resuelve el problema de no conocer los valores de una superclase o querer establecer valores predeterminados.

Todo lo que tienes que hacer es crear una instancia de la superclase, usando la conveniencia init, justo en el init de la subclase. Luego llamas al designado init del super usando la instancia que acabas de crear.

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
2018-01-09 16:36



Mire el video WWDC "403 Swift intermedio" a las 18:30 para una explicación detallada de los inicializadores y su herencia. Como lo entendí, considere lo siguiente:

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

Pero ahora considere una subclase Wyrm: Un Wyrm es un dragón sin piernas y sin alas. ¡Entonces el Inicializador para un Wyvern (2 patas, 2 alas) está equivocado! Ese error se puede evitar si no se puede invocar la conveniencia de Wyvern-Initializer, sino solo el inicializador completo designado:

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

0
2018-06-09 15:43



¿Por qué no tienes dos inicializadores, uno con un valor predeterminado?

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

0
2018-03-24 20:53



Considere extraer el código de inicialización de su conveniente init() a una nueva función auxiliar foo(), llamada foo(...) para hacer la inicialización en tu subclase.


0
2018-01-11 20:36