Essere o non essere? questo è il dilemma della programmazione! 🤔

Qualsiasi valore, qualsiasi oggetto o entità del nostro codice può avere una duplice forma. Due stati.

Può esistere oppure non esistere.

Peppe, che vuol dire?

Per capire questa affermazione dobbiamo conoscere, anche sommariamente, com’è fatta la memoria (RAM) del nostro dispositivo.

La RAM è la memoria in cui vengono conservate le informazioni momentanee dell’applicazione e dove vive l’app stessa. Esempio: i valori calcolati, le immagini scaricate, i valori delle variabili etc etc (in pratica, tutto ciò che non risiede nella memoria fisica)

Devi immaginare la memoria come ad una tabella. Una tabella dove ogni riga è composta da due campi:

  1. Un campo che rappresenta l’indirizzo di memoria
  2. Ed un altro dove viene conservate l’informazione

Ogni indirizzo di memoria ha sempre associato un valore.

Il problema è che non tutto ciò che creiamo (esempio un var) può avere immediatamente un indirizzo di memoria associato.

Ed è proprio questo “può avere” che ci porterà ad incontrare nuovi problemi, nuove emozioni e nuovi tipi di dato 😂

E tra questi tipi di dato, troviamo gli Optional.

Arriviamoci con calma e partiamo dalle basi dell’opzionalità in Swift.

Cominciamo!

Stato di esistenza

Quando creiamo una variabile e gli assegniamo un valore, sotto il cofano, viene creato un nuovo memory address e, nel suo slot, viene conservato il valore passato.

var etichetta: Type = valore // viene creato un nuovo indirizzo di memoria e nel suo slot viene passato il valore

L’etichetta della var/let (il suo nome) identifica in maniera leggibile (per noi dev) l’indirizzo di memoria associato.

Inizializzata la variabile, cioè assegnato un valore alla sua creazione, il suo slot non può più essere distrutto.

Anche se cancelliamo il valore (esempio nelle stringhe passiamo a stringa vuota) un valore al suo interno è sempre presente:

var text: String = "Ciao come stai?"
print(text) 

text = "" // sto comunque passando un valore allo slot

print(text) // posso accedere allo slot anche se il suo valore è "inesistente"

Questo è il comportamento standard che abbiamo finora utilizzato, cioè di esistenza dell’oggetto creato.

Se fosse però sempre così, oggi non ti ritroveresti qui a leggere questo tutorial 😂

Stato di inesistenza

In certe circostanze un riferimento diretto ad un indirizzo di memoria non esiste o non è ancora stato creato.

Cioè la variabile, l’oggetto, all’interno del nostro codice è definito però, sotto il cofano, il sistema non gli ha ancora creato un indirizzo ed uno slot.

E quando verrà creato, quell’entità non potrà essere gestita normalmente.

Il tipo di dato Optional permette di gestire questi oggetti che hanno una duplice forma.

Dato un lato potrebbero avere un valore, e quindi un indirizzo, dall’altro lato no.

È importantissimo saper riconoscere gli oggetti opzionali perché gestirli in maniera errata comporterà la distruzione dell’applicazione.

Fatta questa premessa teorica, che spero abbia già chiarito alcuni aspetti, vediamo insieme come gestire l’opzionalità in Swift!

Optional Type

Capito che un oggetto può esistere o non esistere, vediamo come gestirlo utilizzando il linguaggio Swift.

Nel particolare, Swift, utilizza un tipo di dato che si chiama Optional. Il tipo opzionale è un Wrap cioè un contenitore di altri type. In pratica, tutti i tipi di dato possono diventare opzionali.

Per definire un type come Optional, devi fare seguire al suo tipo di dato:

  1. ? (punto interrogativo)
  2. ! (punto esclamativo)
var name: String?
var age: Int?

var name2: String!
var age2: Int!

Sia il punto interrogativo che il punto esclamativo attivano l’opzionalità sulla variabile.

? e ! è la sintassi veloce per definire il tipo di dato Optional. Se vuoi cimentarti nella sintassi standard puoi scrivere Optional<String> oppure Optional<Int> etc

Nil

Di default, tutti gli oggetti definiti come Optional, che non sono stati inizializzati, hanno il valore di default nil.

Nil indica che la risorsa sta puntando ad un indirizzo di memoria speciale: l’indirizzo zero. Per sua natura, l’indirizzo zero è inaccessibile.

Di conseguenza, l’accesso al suo valore è proibito e chi vi prova ad accedere manda in crash l’applicazione.

Solamente quando passerò un valore ad un oggetto opzionale creerò il suo indirizzo di memoria reale:

var name: String? // punta all'indirizzo zero - nil

// se la uso qui in maniera sbagliata mando in crash l'app

name = "Giuseppe" // adesso ha un indirizzo reale

// adesso posso usare la var

Siccome sono già nella tua testa ed ho provato questo senso di confusione, rispondo subito alla domande delle domande 😂

Perché esiste l’opzionalità?

In origine, parlo dell’alba della programmazione moderna, la memoria dei dispositivi era davvero limitata.

Quindi, per far fronte a questa mancanza di risorse, gli oggetti all’interno del codice venivano prima dichiarati e poi inizializzati.

Dichiarare significa dire al compilatore: guarda, prima o poi dovrai creare un indirizzo di memoria (ma per ora non farlo e quindi non allocare/occupare memoria).

Mentre, inizializzare si traduce in: mi serve utilizzare questa risorsa quindi dammi un indirizzo libero.

Quando la risorsa non non serviva più, si chiedeva di “deferenziarla” dalla memoria. Cioè, si eliminava il suo riferimento all’indirizzo.

Peppe, ma se oggi abbiamo memoria praticamente infinita perché ancora esistono questi concetti?

Purtroppo deriva da una riproposizione di un concetto presente in “vecchi” linguaggi che non è stato sostituito da nuove features.

Continuo a chiedermi, perché?

Semplice, come al solito, c’entrano i soldi.

Siccome formare gli sviluppatori costava più di assumere un altro programmatore. Di conseguenza si è pensato, per anni, di riproporre i concetti vecchi nei nuovi linguaggi.

In questo modo, l’effort nel passaggio alle nuove tecnologie era ridotto al minimo. Perché, nei nuovi linguaggi si ritrovava sempre qualcosa dei precedenti.

Quindi, per forza di cose, dobbiamo continuare a conviverci.

Null Pointer è il concetto che sta alla base dell’assenza o esistenza di un oggetto all’interno della memoria. Se vuoi approfondire, qui trovi tutto il necessario.

Per fortuna, comunque, il linguaggio Swift ci da il type Optional per gestire questo concetto di assenza e presenza (cosa che nei vecchi linguaggi non ritroviamo – vedi il link nella nota sopra).

Differenza tra ! e ? in Swift

Torniamo a noi e poniamoci il problema di capire qual è la differenza tra il ! ed il ? nel linguaggio Swift. Entrambi attivano l’opzionalità su un tipo di dato. Le differenze le troviamo durante l’utilizzo.

Ipotizziamo di avere due variabili, name e name2 di type String? e String!:

var name: String?
var name2: String! 

Di default hanno il valore nil entrambe. Ora, proviamo ad utilizzare il metodo .count (che ho spiegato in questa lezione):

name?.count
name2.count

La prima differenza che notiamo è che:

  1. la var definita con il ? vuole il simbolo durante l’utilizzo.
  2. con il ! non vuole il simbolo.

Ma non è solo questo.

La variabile con il ? interrogativo non genera errore mentre l’altra si. Perché?

Il simbolo ? messo davanti ad una variabile opzionale puoi leggerlo come: prova ad accedere al valore reale dell’oggetto, se c’è continua. Altrimenti, se è nil, fermati e non continuare.

Il punto interrogativo è un sistema di sicurezza in più sull’utilizzo di oggetti opzionali.

Mentre, la variabile definitiva con il punto esclamativo ! prova sempre ad accedere alla parte reale dell’oggetto e se non c’è, quindi è nil, manda in crash l’applicazione.

Fatal Error: Unexpectedly found nil while unwrapping an Optional value”. Questo è l’errore che compare quando si prova ad accedere forzatamente alla parte reale di una variabile opzionale che non presenta un valore (cioè è ancora nil).

Peppe, ma a questo punto, non conviene mettere tutte le variabili/costanti con il punto interrogativo?

Si.

Però, siccome in alcune parti dello sviluppo delle app, ritroverai oggetti definiti con il punto esclamativo, è importante saperli gestire.

Come li gestisco?

Anche se il type di un oggetto è definito come Type!, durante l’utilizzo puoi comunque usare il punto interrogativo. Solamente che spetterà a te sviluppatore inserirlo (cosa che con il Type? è obbligatorio).

var vatNumber: String!

vatNumber?.count // ora non manda in crash l'app

Unwrap

Se da un lato l’opzionalità in Swift sembra portare dei benefici durante la dichiarazione delle risorse, dall’altro porta delle complicazioni quando si provano ad utilizzare queste var/let per eseguire calcoli.

Immaginiamo di voler eseguire una somma tra due stringhe:

var name: String?
var surname: String!

name = "Giuseppe"
surname = " Sapienza"

var nameSurname = name + surname

La somma non è permessa perché il compilatore non sa a cosa far riferimento. Anche se per noi sviluppatori è palese che a quel punto d’esecuzione le var name e surname hanno dei valori, per il compilatore non lo è mai.

Quindi non sa se deve accedere alla parte reale o non reale.

Allora come si fa?

Devi eseguire un’operazione che si chiama Unwrap. Per unwrap si intende estrapolare da un oggetto opzionale la sua parte reale.

Esistono due possibili alternative:

  1. Unwrap forzato
  2. Unwrap opzionale

!

Con il punto esclamativo si esegue un unwrap forzato (cioè si accede alla parte reale – come abbiamo visto sopra).

Durante le operazioni di calcolo bisogna sempre utilizzare la parte reale di una var optional. Quindi, l’esempio visto sopra diventa:

var nameSurname = name! + surname

Perché surname senza punto esclamativo?

Perché il suo type è String! quindi, durante l’utilizzo, è implicito che vogliamo accedere alla sua parte reale.

Dove sta il problema nell’utilizzo dell’unwrap forzato?

Se uno degli oggetti in gioco è nil, l’accesso alla parte reale manda in crash l’applicazione (come abbiamo visto sopra).

if-else

Grazie all’istruzione if-else possiamo evitare di incappare in errore controllando, prima dell’operazione, che i valori non siano nil.

if (name == nil || surname == nil) {
   // una delle due è nil
} else {
    let nameSurname = name! + surname
    print(nameSurname)
}

In questo modo dentro al blocco else si entrerà quando entrambe le variabili hanno un valore reale. In definitiva, se non ci serve controllare il caso in cui gli oggetti siano nil, possiamo scrivere l’if così:

if (name != nil && surname != nil) {
    let nameSurname = name! + surname
    print(nameSurname)
}

Infatti, come ho spiegato nella lezione degli if-else, è sempre meglio mettere l’istruzione importante all’interno del blocco if.

if let

Esiste un’altra sintassi ancora più sicura del classico if-else. Questa consiste nel mettere insieme l’istruzione if con l’assegnazione di un valore opzionale ad una var/let.

Questa operazione si chiama Optional Unwrap.

Più facile a farsi che a dirsi. Ti mostro la sintassi e poi ti do la spiegazione:

var age: Int?
age = 26

if let x = age {
    print("la tua età è di: ", x)
}

L’istruzione la puoi leggere così: Se c’è un valore reale all’interno della var opzionale (age) prendilo ed inseriscilo all’interno della costante (x). Dentro lo scope dell’if, la let x conterrà il valore unwrappato dalla var opzionale.

In questo modo evitiamo di eseguire l’unwrap dentro l’if come visto nel paragrafo precedente.

In pratica, if let su un valore opzionale:

  1. controlla che l’optional sia diverso da nil
  2. Inserisce il valore nella costante se è presente.

L’unica cosa che ci tengo a precisare è che la costante, nel nostro caso chiamata x (ma puoi chiamarla come vuoi), vive solamente all’interno dello scope dell’if.

Infine, molti, utilizzano dare alla costante lo stesso nome della variabile unwrappata. Esempio:

var username: String?

if let username = username { 
    print("il tuo username è: ", username)
}

L’username utilizzato all’interno dello scopo è quello della costante e non la variabile esterna.

Nil coalescing

Certe volte non sarà necessario avere il valore reale di un opzionale ma ci basterà avere un valore di default in sua mancanza.

Per esempio, ipotizziamo che gli utenti abbiamo un punteggio. Se non hanno mai giocato il loro punteggio è di zero.

var points: Int? // metti un valore per testare

let finalPoints = points ?? 0

print("il tuo punteggio è di: ", finalPoints)
print("il tuo punteggio è di: ", points ?? 0) // oppure puoi scrivere così

L’operatore ?? di nil coalescing funziona così: Se c’è un valore all’interno dell’opzionale lo unwrappa e lo passa alla var/let, altrimenti se è nil usa il valore di default messo sulla destra dei due punti interrogativi.

Un altro esempio:

var selectedColor: String?

let buttonColor = selectedColor ?? "Red"

Il valore da mettere alla destra dei due punti dipende dal tipo di dato dell’oggetto opzionale.

I dizionari opzionali

Nella lezione sui dictionary del linguaggio Swift ti ho fatto notare che i valori associati ad una chiave sono di tipo Optional.

Dato che ora abbiamo gli strumenti per risolvere quei problemi, vediamo subito come fare.

let comuni: [String : [String]] = [
    "A" : ["Acireale", "Ancona", "Arezzo"],
    "B" : ["Bologna", "Bari", "Benevento"]
]

let a = comuni["A"] // è di type [String]?

Anche se il valore della chiave è definito come non optional, di default viene aggiunto dal sistema. Nel nostro esempio, l’accesso ai comuni che cominciano per “A” restituisce un array di stringhe opzionali.

Esercizio

Come esercizio ti assegno quello di:

  • Provare ad aggiungere agli array dei comuni dei nuovi valori. Quindi, una volta recuperata la chiave “A”, cioè il suo array, aggiungi un valore e ricordati di aggiornare il dizionario di conseguenza.
  • Aggiungi un nuovo valore al dizionario (una coppia di chiave-valore). Esempio i comuni che cominciano per “C”.

In caso di problemi, scrivimi pure un commento qui sotto.

Considerazioni

Tony Hoare, l’inventore del null pointer cioè il concetto che sta alla base dell’opzionalità, in un’intervista ha dichiarato che la sua invenzione è stata causa di miliardi di danni nella produzione di software.

L’opzionalità del linguaggio Swift è una pezza al problema. Una pezza elegante di facile gestione.

Noi siamo molto ma molto fortunati rispetto a chi scrive codice negli altri linguaggi. Dove, ti assicuro, le cose non si gestiscono con ? e !.

Ad ogni modo, da qui in avanti, troverai questo concetto sopratutto nello sviluppo delle app. Tanti componenti progettati da Apple ne fanno uso e quindi, ti consiglio, di salvarti il link per poi rinfrescati la memoria quando ne avrai di bisogno.

Buona Programmazione!

Back to: Swift Tour: Corso gratuito linguaggio di programmazione Swift > Le basi del linguaggio Swift

🔥
Iscriviti al corso
Segui le lezioni, completa gli esercizi ed ottieni l’attestato

👨‍💻
Hai bisogno d’aiuto?
Scrivi un commento in fondo alla lezione

 

L’unica domanda stupida è quella che non si fa! 

TUTORIAL A CURA DI

Giuseppe Sapienza

Ti è piaciuto l’articolo?
Condividilo con i tuoi amici Dev

Condividi su facebook
Condividi su twitter
Condividi su linkedin
Condividi su telegram