Error en la clase Swift: Propiedad no inicializada en la llamada a super.init

Tengo dos clases, Shape y Square

 class Shape { var numberOfSides = 0 var name: String init(name:String) { self.name = name } func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } } class Square: Shape { var sideLength: Double init(sideLength:Double, name:String) { super.init(name:name) // Error here self.sideLength = sideLength numberOfSides = 4 } func area () -> Double { return sideLength * sideLength } } 

Con la implementación anterior, recibo el error:

 property 'self.sideLength' not initialized at super.init call super.init(name:name) 

¿Por qué tengo que establecer self.sideLength antes de llamar a super.init ?

Cita de The Swift Programming Language, que responde a su pregunta:

“El comstackdor de Swift realiza cuatro valiosas comprobaciones de seguridad para asegurarse de que la inicialización en dos fases se complete sin error:”

Comprobación de seguridad 1 “Un inicializador designado debe garantizar que todas las” propiedades introducidas por su clase se inicialicen antes de delegar en un inicializador de superclase “.

Extracto de: Apple Inc. “The Swift Programming Language”. IBooks. https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11

Swift tiene una secuencia de operaciones muy clara y específica que se realiza en inicializadores. Comencemos con algunos ejemplos básicos y trabajemos hasta llegar a un caso general.

Tomemos un objeto A. Lo definiremos de la siguiente manera.

 class A { var x: Int init(x: Int) { self.x = x } } 

Observe que A no tiene una superclase, por lo que no puede llamar a una función super.init () ya que no existe.

OK, así que ahora vamos a la subclase A con una nueva clase llamada B.

 class B: A { var y: Int init(x: Int, y: Int) { self.y = y super.init(x: x) } } 

Esto es una desviación de Objective-C donde [super init] normalmente se llamaría primero antes que cualquier otra cosa. No es así en Swift. Usted es responsable de asegurarse de que sus variables de instancia estén en un estado constante antes de hacer cualquier otra cosa, incluidos los métodos de llamada (que incluyen el inicializador de su superclase).

Se debe llamar al “super.init ()” después de inicializar todas sus variables de instancia.

En el video “Swift Intermedio” de Apple (puede encontrarlo en la página de recursos de video de desarrolladores de Apple https://developer.apple.com/videos/wwdc/2014/ ), alrededor de las 28:40, se dice explícitamente que todos los inicializadores en debe llamarse a la superclase DESPUÉS de que inicialice sus variables de instancia.

En Objective-C, fue al revés. En Swift, dado que todas las propiedades deben inicializarse antes de su uso, primero debemos inicializar las propiedades. Esto está destinado a evitar una llamada a la función anulada desde el método “init ()” de la superclase, sin inicializar primero las propiedades.

Entonces, la implementación de “Square” debería ser:

 class Square: Shape { var sideLength: Double init(sideLength:Double, name:String) { self.sideLength = sideLength numberOfSides = 4 super.init(name:name) // Correct position for "super.init()" } func area () -> Double { return sideLength * sideLength } } 

De los documentos

Verificación de seguridad 1

Un inicializador designado debe garantizar que todas las propiedades introducidas por su clase se inicialicen antes de delegar en un inicializador de superclase.


¿Por qué necesitamos un control de seguridad como este?

Para responder a esto, permite pasar rápidamente por el proceso de inicialización.

Inicialización bifásica

La inicialización de clase en Swift es un proceso de dos fases. En la primera fase, a cada propiedad almacenada se le asigna un valor inicial por parte de la clase que lo introdujo. Una vez que se ha determinado el estado inicial para cada propiedad almacenada, comienza la segunda fase y cada clase tiene la oportunidad de personalizar sus propiedades almacenadas aún más antes de que la nueva instancia se considere lista para su uso.

El uso de un proceso de inicialización de dos fases hace que la inicialización sea segura, a la vez que brinda una flexibilidad completa a cada clase en una jerarquía de clases. La inicialización en dos fases evita que se acceda a los valores de propiedad antes de que se inicialicen , y evita que los valores de propiedad se establezcan de forma inesperada en un valor diferente por otro inicializador.

Entonces, para asegurarse de que el proceso de inicialización de dos pasos se realiza como se define anteriormente, hay cuatro controles de seguridad, uno de ellos es,

Verificación de seguridad 1

Un inicializador designado debe garantizar que todas las propiedades introducidas por su clase se inicialicen antes de delegar en un inicializador de superclase.

Ahora, la inicialización de dos fases nunca habla de orden, pero esta verificación de seguridad, introduce super.init que se ordenará, después de la inicialización de todas las propiedades.

La verificación de seguridad 1 puede parecer irrelevante ya que, la inicialización bifásica impide que se pueda acceder a los valores de propiedad antes de que se inicialicen , sin esta verificación de seguridad 1.

Como en esta muestra

 class Shape { var name: String var sides : Int init(sides:Int, named: String) { self.sides = sides self.name = named } } class Triangle: Shape { var hypotenuse: Int init(hypotenuse:Int) { super.init(sides: 3, named: "Triangle") self.hypotenuse = hypotenuse } } 

Triangle.init ha inicializado, cada propiedad antes de ser utilizada. Entonces, el control de seguridad 1 parece irrelevante,

Pero luego podría haber otro escenario, un poco complejo,

 class Shape { var name: String var sides : Int init(sides:Int, named: String) { self.sides = sides self.name = named printShapeDescription() } func printShapeDescription() { print("Shape Name :\(self.name)") print("Sides :\(self.sides)") } } class Triangle: Shape { var hypotenuse: Int init(hypotenuse:Int) { self.hypotenuse = hypotenuse super.init(sides: 3, named: "Triangle") } override func printShapeDescription() { super.printShapeDescription() print("Hypotenuse :\(self.hypotenuse)") } } let triangle = Triangle(hypotenuse: 12) 

Salida:

 Shape Name :Triangle Sides :3 Hypotenuse :12 

Aquí si hubiéramos llamado al super.init antes de configurar la hypotenuse , la llamada a super.init habría llamado printShapeDescription() y dado que se ha anulado, primero se recurriría a la implementación de la clase Triangle de printShapeDescription() . printShapeDescription() de la clase Triangle accede a la hypotenuse una propiedad no opcional que aún no se ha inicializado. Y esto no está permitido, ya que la inicialización de dos fases evita que se acceda a los valores de propiedad antes de que se inicialicen

Así que asegúrese de que la inicialización de Dos fases se realiza como se define, debe haber un orden específico de llamadas super.init , y es decir, después de inicializar todas las propiedades introducidas por self clase self , por lo tanto, necesitamos una verificación de seguridad 1

Perdón por el feo formato. Simplemente ponga un carácter de pregunta después de la statement y todo estará bien. Una pregunta le dice al comstackdor que el valor es opcional.

 class Square: Shape { var sideLength: Double? // <=== like this .. init(sideLength:Double, name:String) { super.init(name:name) // Error here self.sideLength = sideLength numberOfSides = 4 } func area () -> Double { return sideLength * sideLength } } 

Edit1:

Hay una mejor manera de omitir este error. De acuerdo con el comentario de jmaschad, no hay ninguna razón para usar opcional en su caso porque los opcionales no son cómodos de usar y siempre debe verificar si opcional no es nulo antes de acceder a él. Entonces, todo lo que tiene que hacer es inicializar el miembro después de la statement:

 class Square: Shape { var sideLength: Double=Double() init(sideLength:Double, name:String) { super.init(name:name) self.sideLength = sideLength numberOfSides = 4 } func area () -> Double { return sideLength * sideLength } } 

Edit2:

Después de dos negativos en esta respuesta, encontré una forma aún mejor. Si desea que el miembro de la clase se inicialice en su constructor, debe asignarle un valor inicial dentro de contructor y antes de la llamada a super.init (). Me gusta esto:

 class Square: Shape { var sideLength: Double init(sideLength:Double, name:String) { self.sideLength = sideLength // <= before super.init call.. super.init(name:name) numberOfSides = 4 } func area () -> Double { return sideLength * sideLength } } 

Buena suerte para aprender Swift.

swift lo obliga a inicializar cada var miembro antes de poder usarlo alguna vez. Como no puede estar seguro de lo que sucede cuando está súper turno, se equivoca: es mejor prevenir que lamentar

Eduardo,

Puede modificar el código en su ejemplo de esta manera:

 var playerShip:PlayerShip! var deltaPoint = CGPointZero init(size: CGSize) { super.init(size: size) playerLayerNode.addChild(playerShip) } 

Esto está usando una opción opcional implícitamente desenvuelta.

En la documentación podemos leer:

“Al igual que con los opcionales, si no proporciona un valor inicial cuando declara una propiedad o variable opcional implícitamente desenvuelta, su valor se establece automáticamente en cero”.

Swift no le permitirá inicializar la súper clase sin inicializar las propiedades, al revés de Obj C. Entonces, debe inicializar todas las propiedades antes de llamar a “super.init”.

Vaya a http://blog.scottlogic.com/2014/11/20/swift-initialisation.html . Da una buena explicación a tu problema.

Agregue nil al final de la statement.


 // Must be nil or swift complains var someProtocol:SomeProtocol? = nil // Init the view override init(frame: CGRect) super.init(frame: frame) ... 

Esto funcionó para mi caso, pero puede no funcionar para el tuyo

Solo estás ingresando en el orden incorrecto.

  class Shape2 { var numberOfSides = 0 var name: String init(name:String) { self.name = name } func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } } class Square2: Shape2 { var sideLength: Double init(sideLength:Double, name:String) { self.sideLength = sideLength super.init(name:name) // It should be behind "self.sideLength = sideLength" numberOfSides = 4 } func area () -> Double { return sideLength * sideLength } } 

Es un diseño asombrosamente estúpido.

Considera algo como esto:

 . . . var playerShip:PlayerShip var deltaPoint = CGPointZero init(size: CGSize) { super.init(size: size) playerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100)) playerLayerNode.addChild(playerShip) } . . . 

Esto no es válido, como se indicó anteriormente. Pero también lo es:

 . . . var playerShip:PlayerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100)) var deltaPoint = CGPointZero init(size: CGSize) { super.init(size: size) playerLayerNode.addChild(playerShip) } . . . 

Porque ‘yo’ no se ha inicializado.

Sinceramente espero que este error se resuelva pronto.

(Sí, sé que podría crear un objeto vacío y luego establecer el tamaño, pero eso es simplemente estúpido).