A volte potrebbe tornare utile definire un nuovo tipo di dato i cui valori sono di numero finito. Un classico esempio è la rappresentazione dei giorni della settimana, che tuttavia potremmo rappresentare con un semplice array di stringhe:
let giorniDellaSettimana = ["Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato", "Domenica"]
Un problema di questo approccio è che lo sviluppatore potrebbe commettere un'errore nella digitazione di uno dei valori, dato che nessun tipo di controllo viene effettuato dal compilatore, per poi accorgersene (non necessariamente) in fase di esecuzione.
Swift ci offre il meccanismo delle enumerazioni per risolvere questo problema in modo elegante e al momento della compilazione, consentendo di definire un nuovo tipo con una lista predefinita di valori:
enum GiorniDellaSettimana {
case Lunedì
case Martedì
case Mercoledì
case Giovedì
case Venerdì
case Sabato
case Domenica
}
GiorniDellaSettimana
è adesso un tipo a tutti gli effetti che può essere utilizzato per definire variabili, costanti, parametri di input e ritorno di funzioni, etc:
var oggi: GiorniDellaSettimana
oggi = GiorniDellaSettimana.Sabato
Dato che il compilarore sa che il tipo di oggi
è GiorniDellaSettimana
non è obbligatorio indicare esplicitamente anche il tipo durante l'assegnazione del valore:
var domani: GiorniDellaSettimana
domani = .Domenica
Si noti, nel codiice, l'uso obbligatorio del punto.
Adesso, oltre ad evitare errori di digitazione, lo sviluppatore avrà un ulteriore vantaggio, portato dal sistema di autocompletamento di Xcode: non appena viene digitato il nome del tipo, o semplicemente il simbolo punto, l'editor di Xcode ci presenterà la lista di tutti i possibili valori dell'enumerazione, consentendo la scelta da un menù a discesa:
Chi conosce il linguaggio C non avrà notato nulla di nuovo, ma in Swift le enum
sono molto più potenti.
Valori grezzi (raw values)
La prima differenza con il linguaggio C è che ogni valore di una enum
non ha un corrispondete valore intero. Considerando l'esempio precedente, in C, Lunedi
avrebbe avuto un valore di 0, mentre in Swift il suo valore è proprio Lunedì
. Tuttavia possiamo decidere di associare un valore grezzo (in inglese raw value) ad ogni elemento della enumerazione, come in C, assegnando un tipo all'enumerazione all'atto di dichiarazione:
enum GiorniDellaSettimana2: Int {
case Lunedì = 1
case Martedì = 2
case Mercoledì = 3
case Giovedì = 4
case Venerdì = 5
case Sabato = 6
case Domenica = 7
}
Possiamo utilizzare normalmente GiorniDellaSettimana2
senza preoccuparci del valore grezzo, ma nel caso ne avessimo di bisogno, possiamo accedervi tramite la proprietà rawValue
:
let dopodomani: GiorniDellaSettimana2 = .Mercoledì
dopodomani.rawValue // 3
Ad esempio, questo potrebbe tornarci utile per calcolare la differenza tra due giorni della settimana:
func giorniDa(inizio: GiorniDellaSettimana2, a: GiorniDellaSettimana2) -> Int {
if inizio.rawValue > a.rawValue {
return inizio.rawValue - a.rawValue
} else {
return a.rawValue - inizio.rawValue
}
}
var inizioSettimana = GiorniDellaSettimana2.Lunedì
giorniDa(inizio: inizioSettimana, a: dopodomani) // 2
Possiamo anche utilizzare il valore grezzo come indice per inizializzare una variabile con uno dei valori della enum
:
var altroGiorno = GiorniDellaSettimana2(rawValue: 5) // Venerdì
A differenza del linguaggio C, i valori grezzi assegnati ad ogni elemento delle enum
possono anche essere non consecutivi. Inoltre il valore grezzo può essere di qualsiasi altro tipo valido, quindi anche diverso da Int
.
Valori associati
In alternativa ad un valore grezzo, un membro di una enumerazione può avere zero o molteplici valori associati. In questo caso ogni membro può anche avere un tipo associato diverso dall'altro.
Supponiamo ad esempio di definire una enum
che ci dia informazioni sul risultato dell'operazione di prelievo da un Bancomat. Abbiamo due possili casi: quello in cui l'operazione va a buon fine, e il caso opposto, in cui si verifica un errore (ad esempio perchè la disponibilità non è sufficiente). Vogliamo inoltre associare al risultato il valore del saldo dopo il prelievo, sfruttando i valori associati:
enum RisultatoPrelievo {
case Positivo(Int)
case Errore(String)
}
var saldo = 100
func preleva(importo: Int) -> RisultatoPrelievo {
if importo > saldo {
saldo -= importo
return RisultatoPrelievo.Errore("Non hai abbastanza fondi")
} else {
return RisultatoPrelievo.Positivo(saldo-importo)
}
}
Se effettuiamo due prelievi:
var p1 = preleva(50) // Positivo(50)
var p2 = preleva(150) // Errore("Non hai abbastanza fondi")
p1
e p2
saranno di tipo RisultatoPrelievo
.
Se è necessario accedere al valore associato di una enum è necessario usare un'istruzione switch
con il meccanismo del binding:
switch p1 {
case .Positivo(let disponibilità):
print("Hai ancora \(disponibilità) euro")
case let .Errore(errore):
print(errore)
}
Notare che è possibile indicare la parola riservata let
(o var
) in due diverse posizioni.
Metodi mutanti delle enumerazioni
A differenza del linguaggio C, ed in modo simile a quanto avviene per le strutture, su Swift è possibile definire dei metodi che interagiscono con le istanze delle enumerazioni. Tuttavia, poiché una enumerazione è un value type, e poiché ciò implica una copia durante l'operazione di assegnazione, un metodo che cambia una proprietà di un'istanza di enum avrebbe effetto solo su quella corrente e non sulle eventuali copie. Pertanto è necessario marcare esplicitamente un tale metodo con la parola riservata mutating
, informando il compilatore Swift del fatto che lo sviluppatore è a conoscenza di questo comportamento.
enum Interruttore {
case Acceso
case Spento
mutating func invertiStato() {
if self == .Acceso {
self = .Spento
} else {
self = .Acceso
}
}
}
var statoLampadina = Interruttore.Acceso
statoLampadina.invertiStato()
Notare l'uso della parola riservata self
per ottenere il riferimento all'instanza corrente.
Il playground con tutti gli esempi di questa lezione è disponibile su GitHub.