Class no implementa los miembros obligatorios de su superclass

Así que actualicé a Xcode 6 beta 5 hoy y me di count de que recibí errores en casi todas mis subclasss de las classs de Apple.

El error dice:

La class 'x' no implementa los miembros obligatorios de su superclass

Aquí hay un ejemplo que elegí porque esta class es actualmente bastante liviana por lo que será fácil de publicar.

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.networkingColor(), 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? Llamo a un inicializador designado.

De un empleado de Apple en los foros de desarrolladores:

"Una forma de declarar al comstackdor y al progtwig integrado que realmente no quiere ser compatible con NSCoding es hacer algo como esto:"

 requinetworking init(coder: NSCoder) { fatalError("NSCoding not supported") } 

Si sabe que no quiere ser compatible con NSCoding, esta es una opción. He adoptado este enfoque con muchos de mis códigos SpriteKit, ya que sé que no lo cargaré desde un guión gráfico.


Otra opción que puede tomar que funciona bastante bien es implementar el método como un inicio de conveniencia, como sigue:

 convenience requinetworking init(coder: NSCoder) { self.init(stringParam: "", intParam: 5) } 

Tenga en count la llamada a un inicializador en self . Esto le permite tener que usar solo los valores ficticios para los parameters, a diferencia de todas las properties no opcionales, al time que se evita arrojar un error fatal.


La tercera opción, por supuesto, es implementar el método al llamar super e inicializar todas sus properties no opcionales. Debería tomar este enfoque si el object es una vista cargada desde un guión gráfico:

 requinetworking init(coder aDecoder: NSCoder!) { foo = "some string" bar = 9001 super.init(coder: aDecoder) } 

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 completamente.

  1. Si un protocolo especifica un inicializador como un método requerido, ese inicializador debe marcarse utilizando la palabra key requinetworking de Swift.
  2. Swift tiene un set especial de reglas de inheritance con respecto a los methods init .

El tl; dr es el siguiente:

Si implementa cualquier inicializador, ya no henetworkingará ninguno de los inicializadores designados de la superclass.

Los únicos inicializadores, si los hay, que henetworkingarán, son los iniciadores de conveniencia de súper class que apuntan a un inicializador designado que pasó a anular.

Entonces … ¿listo para la versión larga?


Swift tiene un set especial de reglas de inheritance con respecto a los methods init .

Sé que este fue el segundo de los dos puntos que hice, pero no podemos entender el primer punto, o por qué la palabra key requinetworking existe hasta que entendamos 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 es de la documentation de Apple que se encuentra aquí .

De los documentos de Apple:

A diferencia de las subclasss en Objective-C, las subclasss Swift no henetworkingan sus inicializadores de superclass de forma pnetworkingeterminada. El enfoque de Swift evita una situación en la que un inicializador simple de una superclass es henetworkingado por una subclass más especializada y se usa para crear una nueva instancia de la subclass que no se inicializa completamente o correctamente.

Énfasis en el mío.

Entonces, directamente desde los documentos de Apple allí, vemos que las subclasss Swift no siempre (y generalmente no) henetworkingan los methods init su superclass.

Entonces, ¿cuándo henetworkingan de su superclass?

Hay dos reglas que definen cuándo una subclass henetworkinga los methods init de su padre. De los documentos de Apple:

Regla 1

Si su subclass no define ningún inicializador designado, automáticamente henetworkinga a todos los inicializadores designados de su superclass.

Regla 2

Si su subclass proporciona una implementación de todos sus inicializadores designados de superclass, ya sea henetworkingándolos según la regla 1 o proporcionando una implementación personalizada como parte de su definición, entonces henetworkinga automáticamente todos los inicializadores de conveniencia de superclass.

La regla 2 no es particularmente relevante para esta conversación porque el init(coder: NSCoder) es poco probable que sea un método de conveniencia.

Por lo tanto, su class InfoBar estaba henetworkingando el inicializador requinetworking hasta el momento en que agregó init(team: Team, size: CGSize) .

Si no hubiese proporcionado este método init y, en su lugar, hiciera opcionales las properties agregadas de su InfoBar o les haya proporcionado los valores pnetworkingeterminados, entonces habría henetworkingado el init(coder: NSCoder) . Sin embargo, cuando agregamos nuestro propio inicializador personalizado, dejamos de henetworkingar los inicializadores designados de nuestra superclass (y los inicializadores de conveniencia que no apuntaban a los inicializadores que implementamos).

Entonces, como un ejemplo simplist, 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 llamada.

introduzca la descripción de la imagen aquí

Si esto fuera Objective-C, no tendría ningún problema henetworkingar. Si inicializamos una Bar con initWithFoo: en Objective-C, la propiedad self.bar sería simplemente nil . Probablemente no sea excelente, pero es un estado perfectamente válido para el object. No es un estado perfectamente válido para que el object Swift se encuentre. self.bar no es opcional y no puede ser nil .

Nuevamente, la única forma en que henetworkingamos los inicializadores es no proporcionar los nuestros. Entonces, si tratamos de henetworkingar borrando init(foo: String, bar: String) , como tal:

 class Bar: Foo { var bar: String } 

Ahora hemos vuelto a henetworkingar (tipo de), pero esto no se comstackrá … y el post de error explica exactamente por qué no henetworkingamos los methods init superclass:

Problema: la class 'Bar' no tiene inicializadores.

Fix-It: la 'barra' de propiedad almacenada sin inicializadores impide inicializadores sintetizados

Si hemos agregado properties almacenadas en nuestra subclass, no existe una forma posible de crear una instancia válida de nuestra subclass con los inicializadores de la superclass, que posiblemente no podrían conocer las properties almacenadas de nuestra subclass.


Bueno, bueno, ¿por qué tengo que implementar init(coder: NSCoder) ? ¿Por qué se requinetworking ?

Los methods de init de Swift pueden jugar con un set especial de reglas de inheritance, pero la conformidad del protocolo todavía se henetworkinga en la cadena. Si una class padre cumple con un protocolo, sus subclasss deben cumplir con ese protocolo.

Normalmente, esto no es un problema, ya que la mayoría de los protocolos solo requieren methods que no cumplen con las reglas especiales de inheritance en Swift, por lo que si henetworkingas de una class que cumple con un protocolo, también henetworkingarás todos los methods o properties que permiten a la class satisfacer la conformidad del protocolo.

Sin embargo, recuerde, los methods de init de Swift juegan con un set especial de reglas y no siempre se henetworkingan. Debido a esto, una class que se ajusta a un protocolo que requiere methods init especiales (como NSCoding ) requiere que la class marque esos methods init según sea requinetworking .

Considere este ejemplo:

 protocol InitProtocol { init(foo: Int) } class ConformingClass: InitProtocol { var foo: Int init(foo: Int) { self.foo = foo } } 

Esto no comstack. Genera la siguiente advertencia:

Problema: el requisito del inicializador 'init (foo :)' solo puede satisfacerse con un inicializador 'requerido' en una class no final 'ConformingClass'

Fix-It: Insertar requerido

Quiere que haga necesario el init(foo: Int) . También podría hacerlo feliz haciendo que la class sea final (es decir, no se puede henetworkingar la class).

Entonces, ¿qué sucede si subclasss? A partir de este punto, si subclasss, estoy bien. Si agrego cualquier inicializador, de repente ya no henetworkingar init(foo:) . Esto es problemático porque ahora ya no estoy conforme con InitProtocol . No puedo subclass de una class que se ajusta a un protocolo y luego de repente decido que ya no quiero conformarme con ese protocolo. He henetworkingado la conformidad del protocolo, pero debido a la forma en que Swift funciona con la inheritance del método init , no he henetworkingado parte de lo que se requiere para cumplir con ese protocolo y debo implementarlo.


De acuerdo, todo tiene sentido. ¿Pero por qué no puedo get un post de error más útil?

Podría decirse que el post de error podría ser más claro o mejor si especificara que su class ya no se ajustaba al protocolo NSCoding henetworkingado y que para solucionarlo necesita implementar init(coder: NSCoder) . Por supuesto.

Pero Xcode simplemente no puede generar ese post porque, en realidad, no siempre será el problema real al no implementar o henetworkingar un método requerido. Hay al less otra razón para hacer que se requinetworking methods init además de la conformidad del protocolo, y eso es un método de fábrica.

Si quiero escribir un método de fábrica apropiado, necesito especificar el tipo de retorno para ser Self (el equivalente de Swift de Objective-C's instanceType ). Pero para hacer esto, realmente necesito usar un método de inicializador requinetworking .

 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 object de class tipo 'Self' con un valor de tipo de metate debe usar un inicializador 'requerido'

introduzca la descripción de la imagen aquí

Es básicamente el mismo problema. Si subclass Box , nuestras subclasss henetworkingarán la factory método de class. Entonces podríamos llamar a SubclassedBox.factory() . Sin embargo, sin la palabra key requinetworking en el método init(size:) , no se garantiza que las self.init(size:) henetworkingen el self.init(size:) que la factory está llamando.

Por lo tanto, debemos hacer que ese método sea requinetworking si queremos un método de fábrica como este, y eso significa que si nuestra class implementa un método como este, tendremos un método de inicializador requinetworking y nos encontraremos con los mismos problemas que has ejecutado Aquí con el protocolo NSCoding .


En última instancia, todo se networkinguce a la comprensión básica de que los inicializadores de Swift juegan con un set de reglas de inheritance un poco diferente, lo que significa que no se garantiza que henetworkinge los inicializadores de su superclass. Esto sucede porque los inicializadores de la superclass no pueden saber acerca de sus nuevas properties almacenadas y no pudieron crear una instancia de su object en un estado válido. Pero, por diversos motivos, una superclass puede marcar un inicializador según sea requinetworking . Cuando lo hace, podemos emplear uno de los escenarios muy específicos mediante los cuales henetworkingamos el método requinetworking , o debemos implementarlo nosotros mismos.

Sin embargo, el punto principal aquí es que si recibimos el error que ve aquí, significa que su class no está implementando el método en absoluto.

Como quizás un ejemplo final para profundizar en el hecho de que las subclasss Swift no siempre henetworkingan los methods de init 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 comstackr.

introduzca la descripción de la imagen aquí

El post de error que da es un poco engañoso:

Argumento adicional 'b' en llamada

Pero el punto es que Bar no henetworkinga ninguno de los methods init de Foo porque no ha satisfecho ninguno de los dos casos especiales para henetworkingar los methods init de su class padre.

Si esto fuera Objective-C, henetworkingaríamos ese init sin problema, porque Objective-C está perfectamente feliz de no inicializar las properties de los objects (aunque como desarrollador, no debería haber sido feliz con esto). En Swift, esto simplemente no lo hará. No puede tener un estado no válido y henetworkingar los inicializadores de superclass solo puede dar lugar a estados de object no válidos.

¿Por qué surgió este problema? Bueno, el hecho es que siempre ha sido importante (es decir, en Objective-C, desde el día en que comencé a progtwigr Cocoa en Mac OS X 10.0) para hacer frente a los inicializadores que su class no está preparada para manejar. Los docs siempre han sido bastante claros sobre sus responsabilidades al respecto. Pero, ¿cuántos de nosotros nos molestamos en cumplirlos, por completo y al pie de la letra? ¡Probablemente ninguno de nosotros! Y el comstackdor no los hizo cumplir; Todo era puramente convencional.

Por ejemplo, en mi subclass de controller de vista Objective-C con este inicializador designado:

 - (instancetype) initWithCollection: (MPMediaItemCollection*) coll; 

… es crucial que se nos pase una colección de elementos de medios reales: la instancia simplemente no puede popup sin una. Pero no he escrito ningún "tapón" para evitar que alguien me inicialice con los huesos desnudos en su lugar. Debería haber escrito una (en realidad, propiamente hablando, debería haber escrito una implementación de initWithNibName:bundle: el inicializador designado henetworkingado); pero era demasiado perezoso para molestarme, porque "sabía" que nunca inicializaría incorrectamente mi propia class de esa manera. Esto dejó un agujero enorme. En Objective-C, alguien puede llamar init , dejando mi ivars sin inicializar, y estamos en el arroyo sin remo.

Swift, maravillosamente, me salva de mí mismo en la mayoría de los casos. Tan pronto como traduje esta aplicación en 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 class, no puedo inicializarme llamando a bare-bones init() . ¡Es un milagro!

Lo que sucedió en la semilla 5 es meramente que el comstackdor se ha dado count de que el milagro no funciona en el caso de init(coder:) , porque en teoría una instancia de esta class podría provenir de una punta y el comstackdor no puede evitar eso, y cuando la plumita se carga, init(coder:) será llamado. Entonces el comstackdor te hace escribir el tapón explícitamente. Y muy bien también.

añadir

 requinetworking init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) }