Una delle caratteristiche di Swift è il fatto di essere un linguaggio sicuro per design, fornendo opportuni meccanismi per evitare l'esecuzione di codice non corretto, ed il verificarsi di effetti collaterali e inaspettati. Abbbiamo già visto che Swift è fortemente tipizzato e questo è uno dei motivi che lo rende sicuro. Il secondo meccanismo che possiede il linguaggio a tal riguardo è quello degli optional.
Un tipo optional consente di rappresentare esattamente due valori:
- assenza di valore
- un valore di qualche tipo specifico
Dato che questa definizione potrebbe sembrare un po' astratta, facciamo un esempio. Supponiamo di avere una variabile che contiene il valore inserito da un utente in un campo di testo:
var input: String
Immaginiamo di volere che l'utente inserisca un numero (ad esempio l'anno di nascita). Tale valore dovrà essere convertito in un intero su cui effettuare delle operazioni (ad esempio il confronto con un altro anno), utilizzando uno degli inizializzatore del tipo Int
:
var year = Int(input)
Se siamo fortunati, input
contiene una stringa che rappresenta un numero (ad esempio "1980"
); ma cosa accadrebbe se l'utente avesse digitato "1980ab"
? Il valore numerico non potrebbe essere determinato. Altri linguaggi di programmazione avrebbero restituito o qualche valore sentinella (ad esempio -1) o sollevato un'eccezione mandando in crash l'esecuzione.
In Swift, invece, l'inizializzatore Int(description: String)
restituisce un valore di tipo optional, e questo gli permette di restituire un'"assenza di valore" usando la parola riservata nil
nel caso in cui esso non possa esser determinato. Chi proviene da Objective-C (o da altri linguaggi che utilizzano referenze e puntatori) potrebbe pensare che nil
indica il fatto che una variabile (o costante o valore di ritorno) non punti a niente. In Swift non è così: nil
viene usato anche per tipi di dati che non sono istanze di classi, come ad esempio Int
, Double
o String
(che in Swift non è una classe ma una struttura).
Tuttavia, il valore nil
può essere assegnato solo ad un tipo opzionale.
Se eseguiamo infatti il codice sopra in un Playground di Xcode, usando il tasto Alt e cliccando su year
noteremo che Swift, tramite il meccanismo di type inference, ha assegnato alla variabile year
il tipo:
var year: Int?
Dichiarazione di un optional
In Swift, per definire una variabile o costante (o valore di ritorno) di tipo optional, si usa aggiungere il simbolo ?
(o !
) al nome del tipo che la variabile conterrà nel caso ci sia invece un valore. È come se dicessimo al compilatore: "questa variabile o contiene un valore di questo tipo, oppure non ha valore (nil)":
var aPossibleNumber: Int?
let canBeADouble: Double?
var inputValue: String?
var label: UILabel!
let year: Int?
Ad esempio, year potrà contenere tutti i numeri positivi e negativi interi, più il valore nil
. Potremmo leggere quel segno finale di interpunzione come un'estensione del dominio dei valori possibile con l'aggiunta di un ulteriore valore, quello del nil
.
Non è invece possibile assegnare nil
ad una variabile di tipo non opzionale, come non è possibile utilizzare una variabile di tipo non opzionale senza averla prima inizializzata. In entrambi i casi il compilatore ci avverte, evitando tutta una serie di errori durante l'esecuzione.
Unwrap di un optional
Se eseguiamo il seguente codice:
var aValue: Int?
aValue = 5
println(aValue)
Noteremo (forse con sorpresa) che l'output sarà:
"Optional(5)\n"
Questo avviene perchè Swift effetta un'operazione detta di wrap dei valori di tipo opzionale; di conseguenza, per utilizzarli è necessario prima effettura l'operazione inversa, cioè l'unwrap.
Per accedere al valore del tipo optional, si usa l'operatore !
(force unwrapping):
println(aValue!)
Questa volta otterremo correttamente il valore che ci aspettavamo.
Se si tenta di fare l'unwrap di una variabile che è nil
(senza valore), si ottiene un errore di esecuzione. Pertanto è utile verificare se una variabile opzionale è stata valorizzata prima di effettuarne l'unwrap:
if aValue != nil {
println("il valore è \(aValue!)")
} else {
println("non ho alcun valore :(")
}
Swift fornisce una sintassi alternativa, chiamata optional binding, che permette di controllare ed effettuare l'unwrap di una variabile in un'unica istruzione:
if let theValue = aValue {
println("il valore è \(theValue)")
} else {
println("non ho alcun valore :(")
}
Il compilatore verifica che aValue
sia diverso da nil
, ed in caso positivo ne fa l'unwrap ponendolo nella costante locale theValue
. Essa potrà essere utilizzata direttamente all'interno del primo blocco del costrutto if, senza farne l'unwrap esplicitamente (theValue
sarà di tipo Int
). Se aValue
non sarà valorizzata, allora il test fallirà e verrà eseguito il secondo blocco.
In alternativa è possibile sostituire let
con var
nel caso in cui si voglia modificare il valore della variabile all'interno del blocco if.
Unwrap implicito (Implicit Unwrapped Optionals)
A volte, dal flusso di esecuzione del codice si evince chiaramente che un tipo optional conterrà sicuramente un valore, e in Swift possiamo usare una notazione alternativa per dichiarare un optional del quale non sarà poi necessario effettuare l'unwrap esplicitamente, semplificando il codice. Per far ciò basta aggiungere il simbolo !
(come anticipato sopra) al tipo dell'optional durante la dichiarazione:
var year: Int!
Potremo così utilizzare year
nel nostro codice senza farne l'unwrap o usare la sintassi di optional binding.
È bene sottolineare che non bisogna confondere questa sintassi con il force unwrapping, che usa lo stesso simbolo. Basta ricordare che se appendiamo il simbolo !
ad un tipo, stiamo dichiarando un optional implicito; se, invece, lo stesso simbolo è posto dopo il nome di una variabile (o costante), ne stiamo forzando l'unwrap. Naturalmente possiamo fare l'unwrap di un optional implicito, sebbene ciò sia superfluo.
Se accidentalmente accediamo ad una variabile opzionale implicita e questa non contiene alcun valore, otterremo un errore di esecuzione. È sempre possibile verificare che un opzionale implicito sia diverso da nil
.
L'operatore di nil coalescing
L'operatore di nil coalescing ci fornisce una sintassi compatta per effettuare l'unwrap di un optional che contiene un valore, ma facendo sì che, nel caso in cui esso sia nil
, venga restituito un valore di default. La sintassi fa uso del simbolo ??
. Ad esempio:
var aValue: Int?
let finalValue = aValue ?? 0
Se aValue
non è inizializzato (come in questo caso), finalValue
sarà 0.
Prima di concludere, ecco un piccolo test: che tipo assegnerà Swift a finalValue
? Sarà un Int
o un Int?
? Provate a scoprirlo utilizzando il frammento di codice precedente su un Playground.
Infine, è bene sapere che potremmo riscrivere la stessa espressione di cui sopra con il classico operatore ternario (ereditato dal C), che ancora una volta usa il simbolo ?
:
let finalValue = (aValue != nil) ? aValue! : 0