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


220

Ho due lezioni ShapeeSquare

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 ottengo l'errore:

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

Perché devo impostare self.sideLengthprima di chiamare super.init?


abbastanza sicuro che ciò abbia a che fare con buone pratiche di programmazione, non una vera limitazione tecnica. Se Shape dovesse chiamare una funzione che Square ha scavalcato, Square potrebbe voler usare sideLength, ma non è ancora stato inizializzato. Swift probabilmente ti limita a farlo per caso, costringendoti a inizializzare le tue istanze prima di chiamare la classe base.
cwharris,

3
Gli esempi nel libro sono cattivi. Dovresti sempre chiamare super.init () last per essere sicuri che tutte le proprietà siano state inizializzate, spiegate di seguito.
Pascal,

Risposte:


173

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

"Il compilatore di Swift esegue quattro utili controlli di sicurezza per assicurarsi che l'inizializzazione in due fasi sia completata senza errori:"

Controllo di sicurezza 1 "Un inizializzatore designato deve garantire che tutte le" proprietà introdotte dalla sua classe siano inizializzate prima di delegare fino a un inizializzatore di superclasse ".

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


47
Questo è un cambiamento sbalorditivo rispetto a C ++, C # o Java.
MDJ,

12
@MDJ Certo. Onestamente non vedo neanche il suo valore aggiunto.
Ruben,

20
In realtà sembra essercene uno. Diciamo in C #, il costruttore di superclassi non dovrebbe chiamare metodi sostituibili (virtuali), perché nessuno sa come reagirebbero con una sottoclasse non completamente inizializzata. In Swift è ok, dato che lo stato extra-sottoclasse va bene, quando il costruttore di superclasse è in esecuzione. Inoltre, in Swift tutti i metodi tranne quelli finali sono sostituibili.
MDJ,

17
Lo trovo particolarmente fastidioso perché significa che, se voglio creare una sottoclasse UIView che crea le proprie sottoview, devo inizializzare quelle sottoview senza frame per iniziare e aggiungere i frame in seguito poiché non posso fare riferimento a quello della vista limiti fino a DOPO aver chiamato super.init.
Ash,

5
@Janos se si rende facoltativa la proprietà, non è necessario inizializzarla init.
JeremyP,

105

Swift ha una sequenza molto chiara e specifica di operazioni eseguite negli inizializzatori. Cominciamo con alcuni esempi di base e arriviamo a un caso generale.

Prendiamo un oggetto A. Lo definiremo come segue.

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

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

OK, quindi ora subclassiamo A con una nuova classe chiamata B.

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

Questa è una deviazione dall'Obiettivo-C dove [super init]verrebbe tipicamente chiamato prima di ogni altra cosa. Non così in Swift. Sei responsabile di assicurarti che le variabili dell'istanza siano in uno stato coerente prima di fare qualsiasi altra cosa, inclusi i metodi di chiamata (che include l'inizializzatore della tua superclasse).


1
questo è estremamente utile, un chiaro esempio. grazie!
FullMetalFist

E se avessi bisogno del valore per calcolare y, ad esempio: init (y: Int) {self.y = y * self.x super.init ()}
6rod9

1
usa qualcosa come, init (y: Int, x: Int = 0) {self.y = y * x; self.x = x; super.init (x: x)}, inoltre non puoi chiamare direttamente un costruttore vuoto per la superclasse con riferimento all'esempio precedente, perché i nomi di superclasse A non hanno un costruttore vuoto
Hitendra Solanki,

43

Dai documenti

Controllo di sicurezza 1

Un inizializzatore designato deve garantire che tutte le proprietà introdotte dalla sua classe siano inizializzate prima di delegare fino a un inizializzatore di superclasse.


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

Per rispondere a questo, lascia andare rapidamente il processo di inizializzazione.

Inizializzazione in due fasi

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

L'uso di un processo di inizializzazione in due fasi rende l'inizializzazione sicura, offrendo comunque la massima flessibilità a ciascuna classe in una gerarchia di classi. L'inizializzazione in due fasi impedisce l'accesso ai valori delle proprietà prima che vengano inizializzati e impedisce che i valori delle proprietà vengano impostati su un valore diverso da un altro inizializzatore in modo imprevisto.

Quindi, per assicurarsi che il processo di inizializzazione in due fasi sia eseguito come definito sopra, 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 classe siano inizializzate prima di delegare fino a un inizializzatore di superclasse.

Ora, l'inizializzazione in due fasi non parla mai di ordine, ma questo controllo di sicurezza, introduce super.initda ordinare, dopo l'inizializzazione di tutte le proprietà.

Il controllo di sicurezza 1 potrebbe sembrare irrilevante poiché l' inizializzazione in 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.initha 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 super.initprima di impostare il hypotenuse, la super.initchiamata avrebbe quindi chiamato il printShapeDescription()e poiché questo è stato ignorato, ricadrebbe prima sull'implementazione della classe Triangle di printShapeDescription(). La printShapeDescription()classe Triangle accede alla hypotenuseproprietà non facoltativa che non è stata ancora inizializzata. E ciò non è consentito poiché l' inizializzazione in due fasi impedisce l'accesso ai valori delle proprietà prima che vengano inizializzati

Quindi assicurati che l'inizializzazione a due fasi sia eseguita come definito, ci deve essere un ordine specifico di chiamata super.inite cioè, dopo aver inizializzato tutte le proprietà introdotte dalla selfclasse, quindi abbiamo bisogno di un controllo di sicurezza 1


1
Grande spiegazione, il motivo per cui dovrebbe sicuramente essere aggiunto alla risposta migliore.
Guy Daher,

Quindi in pratica stai dicendo che, poiché le superclassi init possono chiamare una funzione (overriden) ... in cui quella funzione accede alla proprietà delle sottoclassi, quindi per evitare di non avere valori impostati, la chiamata superdeve avvenire dopo che tutti i valori sono stati impostati. OK ha senso. Ti chiedi come ha fatto Objective-C allora e perché hai dovuto chiamare per superprimo?
Miele

In sostanza quello che stai sottolineando a è simile a: collocazione printShapeDescription() prima self.sides = sides; self.name = named; che genererebbe questo errore: use of 'self' in method call 'printShapeDescription' before all stored properties are initialized. L'errore dell'OP viene fornito per ridurre la "possibilità" di errore di runtime.
Miele

Ho usato specificamente la parola "possibilità", perché se printShapeDescriptionfosse stata una funzione che non si riferiva ad selfes. Sarebbe qualcosa di simile a "stampa (" niente "), non ci sarebbero stati problemi. (Eppure, anche per quello il compilatore genererebbe un errore, perché non è super intelligente)
Honey

Beh, objc non era sicuro. Swift è sicuro, quindi gli oggetti che non sono opzionali devono essere non nulli!
Daij-Djan,

36

"Super.init ()" dovrebbe essere chiamato dopo aver inizializzato tutte le variabili dell'istanza.

Nel video "Intermediate Swift" di Apple (puoi trovarlo nella pagina delle risorse video per gli sviluppatori Apple https://developer.apple.com/videos/wwdc/2014/ ), a circa 28:40, si dice esplicitamente che tutti gli inizializzatori in la super classe 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, dobbiamo prima inizializzare le proprietà. Questo ha lo scopo di impedire una chiamata alla funzione ignorata dal metodo "init ()" della super classe, 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
    }
}

1
Non avrei mai immaginato. L'inizializzazione con super dovrebbe essere la prima affermazione è ciò che avrei pensato. !! hm. piuttosto un cambiamento con rapido.
miticalcoder

Perché questo deve venire dopo? Fornisci un motivo tecnico
Masih

14

Ci scusiamo 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 l'opzione opzionale nel caso in cui le opzioni non siano comode nell'uso e si deve sempre verificare se l'opzione non è nulla prima di accedervi. Quindi tutto ciò che devi fare è inizializzare il 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 il modo migliore. Se vuoi che un membro della classe venga inizializzato nel tuo costruttore, devi assegnargli un valore iniziale all'interno del contructor e prima di chiamare 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 per imparare Swift.


Basta passare super.init(name:name)e self.sideLength = sideLength. Dichiarare sideLengthfacoltativo è fuorviante e introduce ulteriori problemi in seguito quando si deve forzare a scartarlo.
Johannes Luong,

Sì, questa è un'opzione. Grazie
fnc12

In realtà puoi semplicemente avere var sideLength: Double, non c'è bisogno di assegnargli un valore iniziale
Jarsen,

E se avessi davvero una costante opzionale. Cosa ci faccio? Devo inizializzare nel costruttore? Non vedo perché devi farlo, ma il compilatore si lamenta in Swift 1.2
Van Du Tran,

1
Perfetto ! tutte e 3 le soluzioni hanno funzionato "?", "String ()", ma il problema per me era che non avevo "assegnato" una proprietà, e quando l'ho fatto ha funzionato! Grazie amico
amico Naishta,

9

swift ti obbliga a inizializzare ogni membro var prima che sia mai / potrebbe mai essere usato. Dal momento che non può essere sicuro di cosa accada quando si trasforma in supers, si sbaglia: meglio prevenire che curare


1
Questo non ha senso IMO perché la classe genitore non dovrebbe avere visibilità sulle proprietà dichiarate nei suoi figli!
Andy Hin,

1
Non è possibile, ma potresti ignorare le cose e iniziare a usare se stesso "prima che lo
faccia

Riesci a vedere qui e i commenti che seguono? Penso che sto dicendo esattamente quello che stai dicendo, cioè stiamo entrambi dicendo che il compilatore vuole essere sicuro piuttosto che dispiaciuto, la mia unica domanda è, quindi come ha fatto ogg-c a risolvere questo problema? Oppure no? In caso contrario, perché richiede ancora che tu scriva super.initnella prima riga?
Miele,

7

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 optional implicitamente da scartare.

Nella documentazione possiamo leggere:

"Come per gli optionals, se non si fornisce un valore iniziale quando si dichiara una variabile o proprietà facoltativa non implicitamente scartata, il suo valore predefinito è automaticamente zero."


Penso che questa sia l'opzione più pulita. Nel mio primo tentativo Swift, avevo una variabile membro di tipo AVCaptureDevice, che non può essere istanziata direttamente, quindi è stato richiesto il codice init (). Tuttavia, ViewController richiede più inizializzatori e non è possibile chiamare un metodo di inizializzazione comune da init (), quindi questa risposta sembra essere l'unica opzione che evita di copiare / incollare il codice duplicato in ogni inizializzatore.
sunetos,


6

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


Buono, durante l'utilizzo con UIView con UIViewController con protocollo
abdul sathar

1

Stai solo iniziando 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
        }
    }

0

Probabilmente riceverò alcuni voti negativi, ma ad essere onesti, la vita è più facile in questo modo:

class CSListServerData<ListItem: CSJsonData>: CSServerData {
    var key: String!
    var type: ListItem.Type!
    var property: CSJsonDataList<ListItem>!

    func construct(_ key: String, _ type: ListItem.Type) -> Self {
        self.key = key
        self.type = type
        property = CSJsonDataList(self, type, key)
        return self
    }

    func construct(_ type: ListItem.Type) { construct("list", type) }

    var list: [ListItem] { property.list }
}

-4

È un design incredibilmente stupido.

Prendi in considerazione qualcosa del genere:

.
.
.
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 così è:

.
.
.
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 errore venga risolto presto.

(Sì, lo so che potrei creare un oggetto vuoto e quindi impostare le dimensioni ma è semplicemente stupido).


2
Questo non è un bug, la sua nuova programmazione è fondamentale per gli OOP.
Hitendra Solanki,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.