Errore nella class Swift: proprietà non inizializzata alla chiamata super.init

Ho due classi, Shape e 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 l’implementazione sopra ho ricevuto l’errore:

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

Perché devo impostare self.sideLength prima di chiamare super.init ?

Citazione da Swift Programming Language, che risponde alla tua domanda:

“Il compilatore di Swift esegue quattro utili controlli di sicurezza per assicurarsi che l’inizializzazione a due fasi sia completata senza errori:”

Controllo di sicurezza 1 “Un inizializzatore designato deve garantire che tutte le” proprietà introdotte dalla sua class siano inizializzate prima di debind fino a un inizializzatore della superclass. ”

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

Swift ha una sequenza di operazioni molto chiara e specifica che viene eseguita negli inizializzatori. Iniziamo con alcuni esempi di base e procediamo verso un caso generale.

Prendiamo un object A. Lo definiremo come segue.

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

Si noti che A non ha una superclass, quindi non può chiamare una funzione super.init () in quanto non esiste.

OK, quindi ora facciamo una sottoclass A con una nuova class chiamata B.

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

Questo è un allontanamento dall’Obiettivo-C in cui [super init] genere chiamato prima di ogni altra cosa. Non così in Swift. Sei responsabile di garantire che le variabili di istanza siano in uno stato coerente prima di fare qualsiasi altra cosa, compresi i metodi di chiamata (che include l’inizializzatore della superclass).

Il “super.init ()” dovrebbe essere chiamato dopo aver inizializzato tutte le variabili di istanza.

Nel video “Intermediate Swift” di Apple (puoi trovarlo nella pagina delle risorse video per sviluppatori Apple https://developer.apple.com/videos/wwdc/2014/ ), alle 28:40 circa, è esplicito che tutti gli inizializzatori in la super class deve essere chiamata DOPO aver inizializzato le variabili di istanza.

In Objective-C, era il contrario. In Swift, poiché tutte le proprietà devono essere inizializzate prima di essere utilizzate, è necessario innanzitutto inizializzare le proprietà. Questo ha lo scopo di prevenire una chiamata alla funzione sovrascritta dal metodo “init ()” di super-class, senza prima inizializzare le proprietà.

Quindi l’implementazione di “Square” dovrebbe essere:

 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 } } 

Dai documenti

Controllo di sicurezza 1

Un inizializzatore designato deve garantire che tutte le proprietà introdotte dalla sua class vengano inizializzate prima di debind fino a un inizializzatore della superclass.


Perché abbiamo bisogno di un controllo di sicurezza come questo?

Per rispondere a questa domanda, lascia andare il processo di inizializzazione in modo rapido.

Inizializzazione in due fasi

L’inizializzazione della class in Swift è un processo a due fasi. Nella prima fase, a ciascuna proprietà memorizzata viene assegnato un valore iniziale dalla class che lo ha introdotto. Una volta determinato lo stato iniziale per ogni proprietà memorizzata, inizia la seconda fase e a ciascuna class viene offerta la possibilità di personalizzare le proprietà archiviate ulteriormente prima che la nuova istanza sia considerata pronta per l’uso.

L’utilizzo di un processo di inizializzazione a due fasi rende l’inizializzazione sicura, pur garantendo una flessibilità completa a ciascuna class in una gerarchia di classi. L’inizializzazione a due fasi impedisce l’accesso ai valori delle proprietà prima dell’inizializzazione e impedisce l’imprevisto dei valori delle proprietà su un valore diverso da parte di un altro inizializzatore.

Quindi, per assicurarsi che il processo di inizializzazione in due fasi sia eseguito come sopra definito, ci sono quattro controlli di sicurezza, uno dei quali è,

Controllo di sicurezza 1

Un inizializzatore designato deve garantire che tutte le proprietà introdotte dalla sua class vengano inizializzate prima di debind fino a un inizializzatore della superclass.

Ora, l’inizializzazione a due fasi non parla mai dell’ordine, ma questo controllo di sicurezza, introduce super.init da ordinare, dopo l’inizializzazione di tutte le proprietà.

Il controllo di sicurezza 1 potrebbe sembrare irrilevante poiché l’ inizializzazione a due fasi impedisce l’accesso ai valori delle proprietà prima che vengano inizializzati , senza questo controllo di sicurezza 1.

Come in questo esempio

 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 inizializzato, ogni proprietà prima di essere utilizzata. Quindi il controllo di sicurezza 1 sembra irrilevante,

Ma poi potrebbe esserci un altro scenario, un po ‘complesso,

 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) 

Produzione :

 Shape Name :Triangle Sides :3 Hypotenuse :12 

Qui, se avessimo chiamato il super.init prima di impostare l’ hypotenuse , la chiamata super.init avrebbe quindi chiamato printShapeDescription() e dato che è stato ignorato, sarebbe prima printShapeDescription() sull’implementazione della class Triangle di printShapeDescription() . La printShapeDescription() della class Triangle accede hypotenuse una proprietà non facoltativa che non è ancora stata inizializzata. E questo non è permesso in quanto l’ inizializzazione a due fasi impedisce l’accesso ai valori delle proprietà prima che siano inizializzati

Quindi assicurati che l’inizializzazione a due fasi sia fatta come definito, ci deve essere uno specifico ordine di chiamata a super.init , e cioè, dopo aver inizializzato tutte le proprietà introdotte self , quindi abbiamo bisogno di un controllo di sicurezza 1

Scusa per la brutta formattazione. Basta inserire un carattere di domanda dopo la dichiarazione e tutto andrà bene. Una domanda dice al compilatore che il valore è facoltativo.

 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:

C’è un modo migliore per saltare questo errore. Secondo il commento di jmaschad, non vi è alcun motivo per utilizzare facoltativamente nel tuo caso perché gli optionals non sono comodi in uso e devi sempre verificare se l’opzione non è nulla prima di accedervi. Quindi tutto ciò che devi fare è inizializzare un membro dopo la dichiarazione:

 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:

Dopo che due aspetti negativi sono arrivati ​​a questa risposta, ho trovato un modo ancora migliore. Se vuoi che il membro della class sia inizializzato nel tuo costruttore, devi assegnargli il valore iniziale all’interno del contructor e prima della chiamata a super.init (). Come questo:

 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 } } 

Buona fortuna nell’apprendimento di Swift.

rapido impone di inizializzare ogni membro var prima che sia mai / potrebbe mai essere utilizzato. Dal momento che non può essere sicuro di cosa succede quando è il turno dei supers, si sbaglia: meglio prevenire che curare

Edward,

Puoi modificare il codice nel tuo esempio in questo modo:

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

Questo sta usando un opzionale implicitamente da scartare.

Nella documentazione possiamo leggere:

“Come con gli optionals, se non si fornisce un valore iniziale quando si dichiara una variabile o una proprietà facoltativa scartata implicitamente, il valore è automaticamente impostato su zero.”

Swift non ti permetterà di inizializzare la super class senza inizializzare le proprietà, invertire Obj C. Quindi devi inizializzare tutte le proprietà prima di chiamare “super.init”.

Per favore vai a http://blog.scottlogic.com/2014/11/20/swift-initialisation.html . Dà una bella spiegazione al tuo problema.

Aggiungi zero alla fine della dichiarazione.


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

Questo ha funzionato per il mio caso, ma potrebbe non funzionare per il tuo

Stai solo entrando nell’ordine sbagliato.

  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 } } 

È un design incredibilmente stupido.

Considera qualcosa come questo:

 . . . 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) } . . . 

Questo non è valido, come notato sopra. Ma lo è anche:

 . . . 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) } . . . 

Perché “sé” non è stato inizializzato.

Spero sinceramente che questo bug sia stato risolto presto.

(Sì, so che potrei creare un object vuoto e poi impostare la dimensione ma è solo stupido).