Así que actualicé a Xcode 6 beta 5 hoy y noté que recibí errores en casi todas mis subclases de las clases de Apple.
El error dice:
La clase 'x' no implementa los miembros requeridos de su superclase
Aquí hay un ejemplo que elegí porque esta clase es bastante ligera, por lo que será fácil publicarla.
class InfoBar: SKSpriteNode { //Error message here
let team: Team
let healthBar: SKSpriteNode
init(team: Team, size: CGSize) {
self.team = team
if self.team == Team.TeamGood {
healthBar = SKSpriteNode(color: UIColor.greenColor(), size:size)
}
else {
healthBar = SKSpriteNode(color: UIColor.redColor(), size:size)
}
super.init(texture:nil, color: UIColor.darkGrayColor(), size: size)
self.addChild(healthBar)
}
}
Entonces mi pregunta es, ¿por qué recibo este error y cómo puedo solucionarlo? ¿Qué es lo que no estoy implementando? Estoy llamando a un inicializador designado.
fuente
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.init(collection:MPMediaItemCollection!)
. Eso permitiríainit(coder:)
pasar nada. Pero luego me di cuenta: no, ahora solo estás engañando al compilador. Pasar nulo no es aceptable, así que tira elfatalError
y sigue adelante. :)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.
required
palabra clave de Swift .init
métodos.El tl; dr es este:
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:
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 2 no es particularmente relevante a esta conversación porque
SKSpriteNode
'sinit(coder: NSCoder)
es poco probable que sea un método de conveniencia.Entonces, su
InfoBar
clase estaba heredando elrequired
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 queInfoBar
las propiedades agregadas de usted fueran opcionales o si les hubiera proporcionado valores predeterminados, entonces todavía habría estado heredandoSKSpriteNode
las suyasinit(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:
Que presenta el siguiente error:
Si esto fuera Objective-C, no tendría problemas para heredar. Si inicializamos a
Bar
coninitWithFoo:
en Objective-C, laself.bar
propiedad simplemente seríanil
. 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 sernil
.Una vez más, la única forma en que heredamos los inicializadores es no proporcionar el nuestro. Entonces, si tratamos de heredar eliminando
Bar
'sinit(foo: String, bar: String)
, como tal: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 :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é esrequired
?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 requiereinit
métodos especiales (comoNSCoding
) requiere que la clase marque esosinit
métodos comorequired
.Considere este ejemplo:
Esto no se compila. Genera la siguiente advertencia:
Quiere que haga el
init(foo: Int)
inicializador requerido. También podría hacerlo feliz haciendo la clasefinal
(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 elInitProtocol
. 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 lainit
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 implementarloinit(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étodosrequired
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-CinstanceType
). Pero para hacer esto, en realidad necesito usar unrequired
método inicializador.Esto genera el error:
Es básicamente el mismo problema. Si subclasificamos
Box
, nuestras subclases heredarán el método de la clasefactory
. Entonces podríamos llamarSubclassedBox.factory()
. Sin embargo, sin larequired
palabra clave en elinit(size:)
método,Box
no se garantiza que las subclases hereden laself.init(size:)
quefactory
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 unrequired
método de inicialización y nos encontraremos exactamente con los mismos problemas que ha encontrado aquí con elNSCoding
protocoloEn ú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 elrequired
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:Esto no se puede compilar.
El mensaje de error que da es un poco engañoso:
Pero el punto es
Bar
que no hereda ninguno deFoo
losinit
métodos porque no ha satisfecho ninguno de los dos casos especiales para heredarinit
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.fuente
¿Por qué ha surgido este problema? Bueno, el hecho es que siempre ha sido importante (es decir, en Objective-C, desde el día en que comencé a programar Cocoa en Mac OS X 10.0) para tratar con inicializadores que su clase no está preparada para manejar. Los documentos siempre han sido muy claros sobre sus responsabilidades a este respecto. Pero, ¿cuántos de nosotros nos molestamos en cumplirlos, completamente y al pie de la letra? ¡Probablemente ninguno de nosotros! Y el compilador no los hizo cumplir; todo era puramente convencional.
Por ejemplo, en mi subclase de controlador de vista Objective-C con este inicializador designado:
... es crucial que se nos pase una colección de elementos de medios reales: la instancia simplemente no puede existir sin una. Pero no he escrito ningún "tapón" para evitar que alguien me inicialice con los huesos descubiertos
init
. Yo debería haber escrito uno (en realidad, propiamente hablando, debería haber escrito una implementación deinitWithNibName:bundle:
, el inicializador designado heredada); pero era demasiado vago para molestarme, porque "sabía" que nunca inicializaría incorrectamente mi propia clase de esa manera. Esto dejó un gran agujero. En Objective-C, alguien puede llamar a los huesosinit
, dejando mis ivars sin inicializar, y estamos arriba del arroyo sin una paleta.Rápido, maravillosamente, me salva de mí mismo en la mayoría de los casos. Tan pronto como traduje esta aplicación a Swift, todo el problema desapareció. ¡Swift crea efectivamente un tapón para mí! Si
init(collection:MPMediaItemCollection)
es el único inicializador designado declarado en mi clase, no puedo inicializar llamando a bare-bonesinit()
. ¡Es un milagro!Lo que sucedió en la semilla 5 es simplemente que el compilador se ha dado cuenta de que el milagro no funciona en el caso de
init(coder:)
, porque en teoría una instancia de esta clase podría provenir de una punta, y el compilador no puede evitar eso, y cuando el seinit(coder:)
cargará la plumilla, se llamará. Entonces el compilador te hace escribir el tapón explícitamente. Y muy bien también.fuente
fatalError
tapón descrito en stackoverflow.com/a/25128815/341994 . Simplemente conviértalo en un fragmento de código de usuario y, a partir de ahora, puede colocarlo donde sea necesario. Toma medio segundo.añadir
fuente