Hay dos piezas absolutamente cruciales de información específica de Swift que faltan en las respuestas existentes que creo que ayudan a aclarar esto por completo.
- Si un protocolo especifica un inicializador como método requerido, ese inicializador debe marcarse con la
required
palabra clave de Swift .
- Swift tiene un conjunto especial de reglas de herencia con respecto a los
init
métodos.
El tl; dr es este:
Si implementa algún inicializador, ya no heredará ninguno de los inicializadores designados de la superclase.
Los únicos inicializadores, si los hay, que heredará, son los inicializadores de conveniencia de súper clase que apuntan a un inicializador designado que usted anuló.
Entonces ... ¿listo para la versión larga?
Swift tiene un conjunto especial de reglas de herencia con respecto a los init
métodos.
Sé que este fue el segundo de los dos puntos que hice, pero no podemos entender el primer punto, o por qué la required
palabra clave existe hasta que comprendamos este punto. Una vez que entendemos este punto, el otro se vuelve bastante obvio.
Toda la información que cubro en esta sección de esta respuesta proviene de la documentación de Apple que se encuentra aquí .
De los documentos de Apple:
A diferencia de las subclases en Objective-C, las subclases Swift no heredan sus inicializadores de superclase de forma predeterminada. El enfoque de Swift evita una situación en la que un inicializador simple de una superclase es heredado por una subclase más especializada y se utiliza para crear una nueva instancia de la subclase que no se inicializa de forma completa o correcta.
El énfasis es mío.
Entonces, directamente desde los documentos de Apple, vemos que las subclases Swift no siempre heredarán (y generalmente no) los init
métodos de sus superclases .
Entonces, ¿cuándo heredan de su superclase?
Hay dos reglas que definen cuándo una subclase hereda los init
métodos de su padre. De los documentos de Apple:
Regla 1
Si su subclase no define ningún inicializador designado, hereda automáticamente todos sus inicializadores designados de superclase.
Regla 2
Si su subclase proporciona una implementación de todos sus inicializadores designados de superclase, ya sea al heredarlos según la regla 1 o al proporcionar una implementación personalizada como parte de su definición, entonces hereda automáticamente todos los inicializadores de conveniencia de superclase.
Regla 2 no es particularmente relevante a esta conversación porque SKSpriteNode
's init(coder: NSCoder)
es poco probable que sea un método de conveniencia.
Entonces, su InfoBar
clase estaba heredando el required
inicializador hasta el punto que agregó init(team: Team, size: CGSize)
.
Si no hubiera proporcionado este init
método y, en su lugar, hubiera hecho que InfoBar
las propiedades agregadas de usted fueran opcionales o si les hubiera proporcionado valores predeterminados, entonces todavía habría estado heredando SKSpriteNode
las suyas init(coder: NSCoder)
. Sin embargo, cuando agregamos nuestro propio inicializador personalizado, dejamos de heredar los inicializadores designados de nuestra superclase (e inicializadores convenientes que no apuntaban a los inicializadores que implementamos).
Entonces, como un ejemplo simplista, presento esto:
class Foo {
var foo: String
init(foo: String) {
self.foo = foo
}
}
class Bar: Foo {
var bar: String
init(foo: String, bar: String) {
self.bar = bar
super.init(foo: foo)
}
}
let x = Bar(foo: "Foo")
Que presenta el siguiente error:
Falta el argumento para el parámetro 'bar' en la llamada.
Si esto fuera Objective-C, no tendría problemas para heredar. Si inicializamos a Bar
con initWithFoo:
en Objective-C, la self.bar
propiedad simplemente sería nil
. Es probablemente no es muy bueno, pero es perfectamente válida del estado para el objeto a ser. Es no un estado perfectamente válido para el objeto Swift para estar en. self.bar
No es un opcional y no puede ser nil
.
Una vez más, la única forma en que heredamos los inicializadores es no proporcionar el nuestro. Entonces, si tratamos de heredar eliminando Bar
's init(foo: String, bar: String)
, como tal:
class Bar: Foo {
var bar: String
}
Ahora volvemos a heredar (más o menos), pero esto no se compilará ... y el mensaje de error explica exactamente por qué no heredamos los init
métodos de superclase :
Problema: la clase 'Bar' no tiene inicializadores
Fix-It: la propiedad 'barra' almacenada sin inicializadores evita los inicializadores sintetizados
Si hemos agregado propiedades almacenadas en nuestra subclase, no hay una manera rápida de crear una instancia válida de nuestra subclase con los inicializadores de la superclase que posiblemente no podrían conocer las propiedades almacenadas de nuestra subclase.
Bien, bien, ¿por qué tengo que implementarlo init(coder: NSCoder)
? ¿Por qué es required
?
Los init
métodos de Swift pueden jugar con un conjunto especial de reglas de herencia, pero la conformidad del protocolo todavía se hereda en la cadena. Si una clase primaria se ajusta a un protocolo, sus subclases deben cumplir con ese protocolo.
Por lo general, esto no es un problema, porque la mayoría de los protocolos solo requieren métodos que no cumplen con las reglas de herencia especiales en Swift, por lo que si está heredando de una clase que se ajusta a un protocolo, también está heredando todos los métodos o propiedades que permiten que la clase satisfaga la conformidad del protocolo.
Sin embargo, recuerde, los init
métodos de Swift se rigen por un conjunto especial de reglas y no siempre se heredan. Debido a esto, una clase que se ajusta a un protocolo que requiere init
métodos especiales (como NSCoding
) requiere que la clase marque esos init
métodos como required
.
Considere este ejemplo:
protocol InitProtocol {
init(foo: Int)
}
class ConformingClass: InitProtocol {
var foo: Int
init(foo: Int) {
self.foo = foo
}
}
Esto no se compila. Genera la siguiente advertencia:
Problema: el requisito de inicializador 'init (foo :)' solo puede cumplirse con un inicializador 'requerido' en la clase no final 'ConformingClass'
Fix-It: se requiere insertar
Quiere que haga el init(foo: Int)
inicializador requerido. También podría hacerlo feliz haciendo la clase final
(lo que significa que no se puede heredar la clase).
Entonces, ¿qué pasa si subclase? Desde este punto, si subclase, estoy bien. Sin embargo, si agrego algunos inicializadores, de repente ya no estoy heredando init(foo:)
. Esto es problemático porque ahora ya no me estoy conformando con el InitProtocol
. No puedo subclase de una clase que se ajusta a un protocolo y luego, de repente, decido que ya no quiero cumplir con ese protocolo. He heredado la conformidad del protocolo, pero debido a la forma en que Swift trabaja con la init
herencia del método, no heredé parte de lo que se requiere para cumplir con ese protocolo y debo implementarlo.
Bien, todo esto tiene sentido. Pero, ¿por qué no puedo obtener un mensaje de error más útil?
Podría decirse que el mensaje de error podría ser más claro o mejor si especifica que su clase ya no se ajusta al NSCoding
protocolo heredado y que para solucionarlo debe implementarlo init(coder: NSCoder)
. Por supuesto.
Pero Xcode simplemente no puede generar ese mensaje porque ese no siempre será el problema real de no implementar o heredar un método requerido. Hay al menos otra razón para hacer init
métodos required
además de la conformidad del protocolo, y son los métodos de fábrica.
Si quiero escribir un método de fábrica adecuado, necesito especificar el tipo de retorno que será Self
(el equivalente de Swift de Objective-C instanceType
). Pero para hacer esto, en realidad necesito usar un required
método inicializador.
class Box {
var size: CGSize
init(size: CGSize) {
self.size = size
}
class func factory() -> Self {
return self.init(size: CGSizeZero)
}
}
Esto genera el error:
La construcción de un objeto de tipo de clase 'Self' con un valor de metatipo debe usar un inicializador 'obligatorio'
Es básicamente el mismo problema. Si subclasificamos Box
, nuestras subclases heredarán el método de la clase factory
. Entonces podríamos llamar SubclassedBox.factory()
. Sin embargo, sin la required
palabra clave en el init(size:)
método, Box
no se garantiza que las subclases hereden la self.init(size:)
que factory
está llamando.
Por lo tanto, debemos hacer ese método required
si queremos un método de fábrica como este, y eso significa que si nuestra clase implementa un método como este, tendremos un required
método de inicialización y nos encontraremos exactamente con los mismos problemas que ha encontrado aquí con el NSCoding
protocolo
En última instancia, todo se reduce a la comprensión básica de que los inicializadores de Swift juegan con un conjunto ligeramente diferente de reglas de herencia, lo que significa que no está garantizado que herede los inicializadores de su superclase. Esto sucede porque los inicializadores de superclase no pueden conocer sus nuevas propiedades almacenadas y no pueden instanciar su objeto en un estado válido. Pero, por varias razones, una superclase puede marcar un inicializador como required
. Cuando lo hace, podemos emplear uno de los escenarios muy específicos por los cuales realmente heredamos el required
método, o debemos implementarlo nosotros mismos.
Sin embargo, el punto principal aquí es que si recibimos el error que ves aquí, significa que tu clase no está implementando el método en absoluto.
Como quizás un último ejemplo para profundizar en el hecho de que las subclases de Swift no siempre heredan los init
métodos de sus padres (lo cual creo que es absolutamente central para comprender completamente este problema), considere este ejemplo:
class Foo {
init(a: Int, b: Int, c: Int) {
// do nothing
}
}
class Bar: Foo {
init(string: String) {
super.init(a: 0, b: 1, c: 2)
// do more nothing
}
}
let f = Foo(a: 0, b: 1, c: 2)
let b = Bar(a: 0, b: 1, c: 2)
Esto no se puede compilar.
El mensaje de error que da es un poco engañoso:
Argumento adicional 'b' en llamada
Pero el punto es Bar
que no hereda ninguno de Foo
los init
métodos porque no ha satisfecho ninguno de los dos casos especiales para heredar init
métodos de su clase padre.
Si esto fuera Objective-C, heredaríamos eso init
sin problema, porque Objective-C está perfectamente feliz de no inicializar las propiedades de los objetos (aunque como desarrollador, no debería haber estado contento con esto). En Swift, esto simplemente no servirá. No puede tener un estado no válido, y heredar inicializadores de superclase solo puede conducir a estados de objeto no válidos.
init(collection:MPMediaItemCollection)
. Debe proporcionar una colección real de elementos de medios; ese es el punto de esta clase. Esta clase simplemente no puede ser instanciada sin una. Analizará la colección e inicializará una docena de variables de instancia. ¡Ese es el objetivo de que este sea el único inicializador designado! Por lo tanto,init(coder:)
no tiene MPMediaItemCollection significativo (o incluso sin sentido) para suministrar aquí; solo elfatalError
enfoque es correcto.