Gli inizializzatori sono metodi speciali di una classe che hanno il compito di preparare le istanze ad essere correttamente utilizzate. Abbiamo già visto che un'inizializzatore viene definito con la parola riservata init
all'interno della definizione di una classe.
In particolare gli inizializzatori hanno la responsabilità ultima di inizializzare correttamente tutte le proprietà non opzionali, in modo che l'istanza sia pronta all'uso. Inoltre questi possono effettuare eventuali altre personalizzazioni se richiesto dalla logica della classe.
Vediamo un esempio:
class Veicolo {
var marca: String
var modello: String
var velocita: Int
init(marca: String, modello: String, velocita: Int) {
self.marca = marca
self.modello = modello
self.velocita = velocita
}
}
Per creare un'istanza di Veicolo
usiamo il nome della classe a cui passiamo gli argomenti definiti nell'inizializzatore:
var v1 = Veicolo(marca: "BMW", modello: "X3", velocita: 280) // {marca "BMW" modello "X3" velocita 280}
Dietro le quinte, il compilatore invoca ed esegue il corpo del metodo init
.
Nella classe Veicolo
sono dichiarate tre proprietà. Se omettessimo la definizione dell'inizializzatore, Swift genererebbe un errore di compilazione, dato che marca
, modello
, e velocita
non hanno un valore di default. Se, invece, tali proprietà fossero dichiarate opzionali:
class Veicolo2 {
var marca: String?
var modello: String?
var velocita: Int?
}
o fossero inizializzate durante la dichiarazione:
class Veicolo3 {
var marca: String = "non definita"
var modello: String = "non definito"
var velocita: Int = 0
}
la presenza dell'inizializzatore sarebbe opzionale. In realtà, in caso come i precedenti, se non viene definito alcun inizializzatore, come in questi due ultimi casi, Swift genererà un inizilizzatore sintetizzato che permette di creare istanze senza passare alcun argomento:
var vv2 = Veicolo2() // {nil nil nil}
var vv3 = Veicolo3() // {marca "non definita" modello "non definito" velocita 0}
Inizializzatori designati e di convenienza
Ogni classe può avere molteplici inizializzatori, con un numero differente di argomenti (overloading) a patto che ognuno di essi assolva la funzione di assicurare la corretta valorizzazione di tutte le proprietà non opzionali:
class Veicolo4 {
var marca: String
var modello: String
var velocita: Int
init(marca: String, modello: String, velocita: Int) {
self.marca = marca
self.modello = modello
self.velocita = velocita
}
init() {
self.marca = ""
self.modello = ""
self.velocita = 0
}
}
var vv4 = Veicolo4() // {marca "" modello "" velocita 0}
Gli inizializzatori della classe Veicolo4
sono detti anche inizializzatori designati.
È anche possibile definire uno o più inizializzatori che richiamino un altro inizializzatore definito all'interno della stessa classe. In questo caso si parla di inizializzatori di convenienza che devono essere marcati esplicitamente con la parola riservata convenience
:
class Veicolo {
var marca: String
var modello: String
var velocita: Int
init(marca: String, modello: String, velocita: Int) {
self.marca = marca
self.modello = modello
self.velocita = velocita
}
convenience init(marca: String) {
self.init(marca: marca, modello: "Non definito", velocita: 0)
}
}
var v2 = Veicolo(marca: "Audi") // {marca "Audi" modello "Non definito" velocita 0}
Una classe può disporre di più inizializzatori di convenienza e ognuno di questi può richiamarne altri dello stesso tipo, creando una catena, a patto che alla fine venga richiamato un inizializzatore designato. Gli inizializzatori designati invece non possono richiamare altri inizializzatori designati (né di convenienza) della stessa classe.
Ereditarietà degli inizializzatori
In realtà esiste un'eccezione in cui un inizializzatore designato può invocare un altro inizializzatore designato: nel caso di ereditarietà, in cui l'inizializzatore delle sottoclasse ha la necessità di invocare l'inizializzatore della superclasse per configurare correttamente le proprietà ereditate:
class Auto: Veicolo {
var tipoCambio: String
init(marca: String, modello: String, velocita: Int, tipoCambio: String) {
self.tipoCambio = tipoCambio
super.init(marca: marca, modello: modello, velocita: velocita)
}
}
L'invocazione dell'inizializzatore della superclasse viene effettuata usando la parola riservata super
, che in generale ci permette di accedere a tutte le proprietà ed i metodi (inclusi gli inizializzatori) della superclasse.
A differenza che in Objective-C, in cui l'inizializzatore della classe padre viene invocato immediatamente, in Swift è obbligatorio che la classe figlia inizializzi prima tutte le proprietà introdotte da tale classe, e poi deleghi l'inizializzazione delle proprietà ereditate all'inizializzatore della classe padre.
Generalmente gli inizializzatori non vengono ereditati. L'eccezione si ha quando una sottoclasse non ha alcun inizializzatore e le eventuali proprietà in essa definite sono inizializzate durante la definizione. Tuttavia, nel caso contrario in cui una sottoclasse ha la necessità di ridefinire un'inizializzatore che ha lo stesso numero di argomenti dell'inizializzatore della superclasse, è necessario marcare esplicitamente l'inizializzatore della sottoclasse con la parola riservata override
:
class Auto: Veicolo {
var tipoCambio: String
init(marca: String, modello: String, velocita: Int, tipoCambio: String) {
self.tipoCambio = tipoCambio
super.init(marca: marca, modello: modello, velocita: velocita)
}
convenience init(tipoCambio: String) {
self.init(marca: "non definita", modello: "non definito", velocita: 0, tipoCambio: tipoCambio)
}
convenience init() {
self.init(tipoCambio: "manuale")
}
override init(marca: String, modello: String, velocita: Int) {
self.tipoCambio = "manuale"
super.init(marca: marca, modello: modello, velocita: velocita)
}
}
var a1 = Auto()
var a2 = Auto(tipoCambio: "automatico")
var a3 = Auto(marca: "Audi", modello: "A3", velocita: 240, tipoCambio: "automatico")
var a4 = Auto(marca: "Volvo", modello: "V40", velocita: 220)
Deinizializzatori
Un deinizializzatore è un metodo speciale che viene invocato quando l'istanza verrà deallocata, cioè nel momento in cui il compilatore rilascia e cancella la spazio di memoria utilizzato per la memorizzazione dell'istanza.
Normalmente, non è necessario definire un deinizializzatore. Tuttavia, possono esserci dei casi d'uso in cui si vogliano eseguire delle operazioni (ad esempio deregistrazione dell'oggetto ad un sistema di notifica) prima che l'instanza venga distrutta. A tal propito Swift mette a disposizione il metodo speciale deinit
che può essere implementato all'interno di una classe. Esso verrà invocato non appena il sistema di Automatic Reference Counting (ARC) determinerà che l'istanza non è più referenziata.
Se aggiungiamo alla classe Auto
il seguente codice:
class Auto: Veicolo {
... // proprietà e metodi
deinit {
print("Auto demolita");
}
...
}
e poi aggiungiamo in coda ad un array di Auto
una nuova istanza, per poi immediatamente rimuoverla:
var parcoAuto = [a3, Auto()]
parcoAuto.removeLast()
vedremo apparire il messaggio definito nel deinizializzatore, segno che il metodo è stato correttamente invocato..
Il playground con tutti gli esempi di questa lezione è disponibile su GitHub.