class - gratis - ¿Por qué los inicializadores Swift no pueden llamar a los inicializadores convenientes en su superclase?
swift 4 tutorial español (7)
¿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
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. Finalmente, se llama al inicializador designado de cada clase con cualquier valor que necesiten, entonces ¿por qué necesito repetirme en el init
B
especificando un valor predeterminado para x
otra vez, cuando la conveniencia init
en A
bien?
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: )
Considere la posibilidad de extraer el código de inicialización de su conveniente init()
a una nueva función auxiliar foo()
, invocar foo(...)
para realizar la inicialización en su subclase.
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 init
designado 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)
}
}
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á a SubClass.init()
que llamará a SuperClass.init(value:)
que llamará a SubClass.init()
.
Las reglas de inicio designadas / de conveniencia están diseñadas para que la inicialización de una clase siempre sea correcta.
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: los inicializadores designados deben llamar a un inicializador designado de su superclase inmediata.
É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:
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 patas ni 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)
}
}