Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. Swift
Code

Introduzione a Swift - Parte 2

by
Difficulty:BeginnerLength:LongLanguages:

Italian (Italiano) translation by Nicola Lamonaca (you can also view the original English article)

Nel primo articolo di questa serie introduttiva a Swift, abbiamo parlato della filosofia di Swift, dato un'occhiata alla sua sintassi, ed evidenziate alcune differenze-chiave rispetto ad Objective-C. In questo articolo, continueremo ad esplorare la sintassi di Swift. Imparerete anche cosa sono gli optionals e vedrete come funziona la gestione della memoria in Swift.

1. Conditionals e Loops

If

Il costrutto if è identico in Swift e in Objective-C, con l'eccezione di due sottili differenze:

  • le parentesi tonde attorno alla variabile condizionale sono opzionali
  • le parentesi graffe sono obbligatorie

Queste sono le uniche due differenze riguardanti il costrutto if rispetto all'Objective-C.

Intervalli

Come abbiamo visto nel primo articolo, Swift include due operatori di range, ..< e ..., per specificare un intervallo di valori. Questi due operatori sono l'operatore di range semi-chiuso e l'operatore di range totalmente chiuso.

Un intervallo semi-chiuso, come 1..<5, rappresenta i valori 1, 2, 3 e 4, 5 escluso. Un intervallo totalmente chiuso, come 1...5, rappresenta i valori da 1 a 5, quest'ultimo incluso.

Gli intervalli possono essere usati nei loop for, negli indici degli array, e persino nei costrutti switch. Date un'occhiata agli esempi che seguono.

Switch

I costrutti switch sono assai più potenti in Swift rispetto a quanto lo siano in Objective-C. In Objective-C, il risultato dell'espressione di un costrutto Switch dev'essere di tipo intero e i valori di ogni istruzione case dovrebbero essere una costante o un'espressione costante. Questo non è il caso di Swift. In Swift, i case: possono essere di qualunque tipo, intervalli inclusi.

In Swift, lo switch non ha alcuna clausola break e non si verifica il fall-through da un case: al successivo. Nello scrivere un blocco switch, dev'essere posta particolare attenzione a ché tutte le condizioni siano coperte dai rispettivi case:, o, in caso contrario, verrà segnalato un errore. Un modo sicuro per coprire tutte le casistiche è quello di includere un case: default a valle del costrutto.

Ecco un esempio di un blocco switch con case: di tipo String:

In Swift, le istruzioni 'case:' non vengono eseguite in cascata di default. Questa è stata una precisa scelta progettuale per evitare errori comuni. Se un caso specifico necessita di fall-through, è possibile utilizzare la keyword fallthrough per indicare questa vostra volontà al compilatore.

Non finisce qui. Swift aggiunge altre 2 features al costrutto switch, ovvero il binding per valore e la clausola where. Il binding per valore è utilizzato con le keywords case let per legare una costante al case: corrispondente. La clausola where aggiunge una condizione extra all'istruzione case: che utilizza la clausola.

Questi due concetti sono meglio espressi tramite esempi. Il seguente blocco di codice mostra come funziona il binding per valore.

La prima istruzione case, case (let x, 0), eseguirà un match dei valori di somePoint per i quali yaxis è uguale a 0 e xaxis arbitrario, ed effettuiamo un binding di xaxis alla costante x per essere utilizzata all'interno dell'istruzione case.

Vediamo adesso un esempio della clausola where in azione.

2. Funzioni e Closures

Funzioni

Definire funzioni e closures in Swift è compito abbastanza facile. I programmatori Objective-C li conoscono meglio come funzioni e blocchi.

In Swift, i parametri di funzione possono avere dei valori di default, cosa che è una reminiscenza dei linguaggi di scripting, come PHP e Ruby.

La sintassi per le funzioni è la seguente:

Per definire una funzione sayHello che prende in input un parametro name di tipo String e restituisce un Bool in caso di successo, scriviamo così:

Per passare un valore di default al parametro name, l'implementazione della funzione assomiglierebbe a questo:

Una feature completamente assente in Object-C sono le tuple. In Swift, le funzioni possono restituire valori multipli in forma di tupla. Le tuple sono trattate come variabili singole, ovvero potete passarle come parametri proprio come fareste con una variabile.

Le tuple sono molto semplici da utilizzare. Infatti, abbiamo già lavorato con le tuple nell'articolo precedente, quando abbiamo enumerato un dizionario. Nel frammento di codice che segue, la coppia chiave/valore è una tupla.

Come si usano le tuple e come è possibile trarne vantaggio? Diamo un'occhiata ad un altro esempio. Modifichiamo la funzione sayHello definita poco fa perché restituisca un valore booleano in caso di successo, così come un messaggio di saluto. Possiamo fare ciò restituendo una tupla, (Bool, String). La funzione sayHello aggiornata appare così:

Le tuple sono rimaste sulla lista dei desideri di molti programmatori Objective-C per molto tempo.

Un'altra peculiarità interessante delle tuple è che possiamo denominare le variabili restituite. Se rivisitiamo l'esempio precedente e diamo dei nomi alle variabili della tupla, otteniamo quanto segue:

Ciò significa che invece di definire una nuova costante per ogni elemento restituito di una tupla, possiamo accedere agli elementi della tupla utilizzando la dot notation, come mostrato nell'esempio di sopra, status.success e status.greeting.

Closures

Le closures in Swift sono l'equivalente dei blocchi in Objective-C. Possono essere definite in maniera inline, passate come parametri, o restituite dalle funzioni. Le utilizziamo esattamente come useremmo i blocchi in Objective-C.

Definire una closure è ugualmente semplice. Di fatto, una funzione è un caso particolare di closure. Quindi non c'è da meravigliarsi se la definizione di una closure assomiglia molto a quella di una funzione.

Le closures sono cittadini di prima classe, ovvero possono essere passate e restituite alle/dalle funzioni proprio come qualunque altro tipo, come Int, String, Bool, eccetera. Le closures sono essenzialmente dei blocchi di codice che possono essere invocati in un secondo tempo e hanno accesso allo scope nel quale sono state definite in origine.

La creazione di una closure anonima è tanto semplice quanto racchiudere un blocco di codice all'interno di parentesi graffe. I parametri e il tipo di ritorno della closure sono separati dal corpo della closure dalla keyword in.

Diciamo che vogliamo definire una closure che restituisce true se un numero è pari, quindi avremmo:

La closure isEven prende un Int come suo unico parametro e restituisce un Bool. Il tipo della suddetta closure è (number: Int) -> Bool o, più brevemente, (Int -> Bool). Possiamo invocare isEven in qualunque punto del nostro codice, allo stesso modo in cui invocheremmo un blocco di codice in Objective-C.

Per passare una closure di questo tipo come parametro di una funzione, usiamo il tipo della closure nella definizione della funzione:

In questo esempio, il parametro verifier della funzione verifyIfEven è una closure che passiamo alla funzione.

3. Classi e Strutture

Classi

È giunto adesso il momento di affondare le mani nell'essenza della programmazione Object-Oriented, ovvero le classi. Le classi, come detto prima, sono definite in un singolo file di implementazione con estensione .swift. Le dichiarazioni di proprietà e metodi sono tutte contenute in quel file.

Creiamo una classe con la parola chiave class seguita dal nome della classe. L'implementazione della classe è racchiusa da una coppia di parentesi graffe. Come in Objective-C, le convenzioni di nomenclatura per le classi impongono di usare il "Camel Case" per i nomi delle classi.

Per creare un'istanza della classe Hotel scriviamo:

In Swift, non è necessario invocare init sugli oggetti, poiché viene già invocato automaticamente.

L'ereditarietà per le classi segue la stessa struttura dell'Objective-C: un carattere di due punti separa il nome della classe da quello della sua superclasse. Nell'esempio seguente, Hotel eredita dalla classe BigHotel.

Come in Objective-C, usiamo la dot notation per accedere alle proprietà di un oggetto. Inoltre, Swift utilizza la dot notation anche per invocare metodi di classe e di istanza, come potete vedere di seguito:

Proprietà

Un'altra differenza con Objective-C è che Swift non distingue tra variabili d'instanza (ivars) e proprietà. Una variabile di istanza è una proprietà.

Dichiarare una proprietà è come definire una variabile o una costante, utilizzando le parole-chiave var e let.L'unica differenza sta nel contesto in cui sono definite, ovvero quello di una classe.

In questo caso, rooms è un valore immutabile, una costante, impostata a 10, e fullRooms è una variabile con valore inziale pari a 0, che può essere modficato in seguito. La regola impone che le proprietà devono essere inizializzate al momento della dichiarazione. L'unica eccezione a questa regola è costituita dagli optionals, che discuteremo tra un attimo.

Proprietà calcolate

Il linguaggio Swift definisce anche quelle che sono proprietà calcolate. Le proprietà calcolate sono nient'altro che metodi getter e setter che non memorizzano un valore. Come indica il loro nome, esse sono calcolate o valutate in maniera on-the-fly.

Sotto è un esempio di proprietà calcolata. Abbiamo modificato la propietà rooms in una var nel corso di tutto l'esempio che segue. Scoprirete più tardi il perché.

Poiché la proprietà description è di sola lettura e contiene solo un'istruzione return, possiamo omettere la keyword get e le parentesi graffe, e tenere solo l'istruzione return. È un'abbreviazione ed è ciò che utilizzeremo d'ora in poi per il resto del tutorial.

Possiamo anche definire proprietà calcolate in lettura/scrittura. Nella nostra classe Hotel, vogliamo una proprietà emptyRooms che memorizzi il numero di stanze libere nell'hotel, ma vogliamo anche aggiornare fullRooms quando impostiamo la proprietà calcolata emptyRooms. Possiamo fare ciò utilizzando la keyword set come mostrato di seguito.

Nel setter di emptyRooms, la costante newValue è visibile e rappresenta il valore passato al setter. È anche importante notare che le proprietà calcolate sono sempre dichiarate come variabili utilizzando la keyword var, perché il loro valore calcolato può cambiare.

Metodi

Abbiamo già trattato prima le funzioni. I metodi non sono niente di più che funzioni legate ad un tipo, come una classe. Nell'esempio seguente, implementiamo un metodo di istanza, bookNumberOfRooms, nella classe Hotel che abbiamo creato prima.

Costruttori

Il costruttore di default per le classi è init. Nella funzione init, impostiamo i valori iniziali dell'istanza appena creata.

Per esempio, se volessimo una sottoclasse di Hotel con 100 stanze, allora necessiteremmo di un costruttore per impostare la proprietà rooms a 100. Ricordate che prima abbiamo cambiato rooms da costante a variabile, all'interno della classe Hotel. Il motivo è che non possiamo cambiare le costanti ereditate in una sottoclasse, infatti solo le variabili ereditate possono essere modificate.

I costruttori possono anche prendere dei parametri in input. L'esempio che segue mostra come.

Ridefinire Metodi e Proprietà calcolate

Questa è una delle cose più interessanti in Swift. In Swift, una sottoclasse può ridefinire sia i metodi che le proprietà calcolate. Per fare ciò, utilizziamo la keyword override. Ridefiniamo la propietà calcolata description nella classe CustomHotel:

Adesso description restituisce il risultato del metodo description della superclasse con la stringa Howdy! come suffisso.

Il bello della ridefinizione dei metodi e delle proprietà calcolate sta nella parola chiave override. Quando il compilatore incontra la parola chiave override, controlla se la superclasse implementa il metodo che si sta ridefinendo. Inoltre, il compilatore controlla se le proprietà e i metodi di una classe sono in conflitto con proprietà o metodi a livelli più elevati nell'albero di ereditarietà.

Non so dire quante volte un errore di battitura in un metodo ridefinito in Objective-C mi abbia causato grattacapi, perché il codice non funzionava. In Swift, il compilatore vi dirà esattamente cosa c'è che non va in queste situazioni.

Strutture

Le strutture, definite con la parola chiave struct, sono più potenti in Swift di quanto lo siano in C o in Objective-C. In C, le strutture definiscono solo valori e puntatori. Le strutture di Swift sono come quelle del C, ma in più supportano le proprietà calcolate e i metodi.

Tutto ciò che potete fare con una classe, potete farlo con una struttura, con due differenze importanti:

  • le strutture non supportano l'ereditarietà come la supportano invece le classi
  • le strutture sono passate per valore, mentre le classi sono passate per riferimento

Ecco alcuni esempi di strutture in Swift:

4. Optionals

Soluzione a un Problema

Gli optionals sono un concetto del tutto nuovo se provenite dall'Objective-C. Essi risolvono un problema che tutti noi programmatori incontriamo. Quando accediamo a una variabile del cui valore non siamo sicuri, solitamente restituiamo un indicatore, noto come sentinella, per indicare che il valore restituito è un non-valore. Vediamo meglio questa situazione con un esempio in Objective-C:

In questo esempio, stiamo cercando di trovare la posizione di @"B" all'interno di someString. Se @"B" è presente, la sua posizione è memorizzata in pos. Ma cosa succede se @"B" non è presente in someString?

La documentazione dice che rangeOfString: restituisce un NSRange con location impostata al valore della costante NSNotFound. Nel caso di rangeOfString:, la sentinella è NSNotFound. Le sentinelle sono utilizzate per indicare che un valore restituito non è valido.

In Cocoa, vi sono numerose applicazioni di questo concetto, ma il valore sentinella differisce da contesto a contesto: 0, -1, NULL, NSIntegerMax, INT_MAX, Nil, eccetera. Il problema per il programmatore è che deve ricordare quale sentinella utilizzare in quale contesto. Se il programmatore non è attento, potrebbe scambiare un valore valido per un valore sentinella e viceversa. Swift viene incontro a questo problema con gli optionals. Citando Brian Lanier: "Gli optionals sono la sentinella per domarle tutte."

Gli optionals hanno due stati: uno stato nil, nel quale l'optional non contiene nessun valore, e un secondo stato, nel quale contiene un valore valido. Pensate agli optionals come ad una scatola con un indicatore che vi dice se il contenuto della scatola è valido oppure no.

Utilizzo

Tutti i tipi in Swift possono diventare degli optionals. Definiamo un optional aggiungendo un carattere ? alla fine della dichiarazione del tipo, in questo modo:

Assegniamo un valore alla scatola di un optional proprio come facciamo con le costanti e le variabili.

Ricordate che gli optionals sono come delle scatole. Quando dichiariamo let someInt: Int?, definiamo una scatola vuota con un valore nil. Assegnando il valore 10 all'optional, la scatola contiene un intero uguale a 10 e il suo indicatore o stato diventa not nil.

Per leggere il contenuto di un optional usiamo l'operatore !. Dobbiamo accertarci che l'optional abbia un valore valido prima di estrarne il contenuto. In caso contrario verrà generato un errore a runtime. Ecco come accediamo al valore memorizzato in un optional:

Questo pattern è così ricorrente in Swift che possiamo semplificare il blocco di codice soprastante utilizzando un binding opzionale con le parole chiave if let. Date un'occhiata al blocco di codice aggiornato qui sotto:

Gli optionals sono l'unico tipo che può prendere in input un valore nil. Le costanti e le variabili non possono essere inizializzate o impostate successivamente a nil. Questo è parte della politica di sicurezza di Swift: tutte le variabili non optionals e le costanti devono avere un valore.

5. Gestione della Memoria

Se ricordate, quando venne introdotto l'ARC, utilizzavamo le keyword strong e weak per definire la proprietà di un oggetto. Anche Swift possiede un modello di proprietà basato su strong e weak, in aggiunta ad un nuovo modello, unowned. Diamo un'occhiata a ciascun modello di proprietà in Swift.

strong

I riferimenti strong sono quelli di default in Swift. Il più delle volte, siamo noi a possedere l'oggetto che stiamo referenziando e noi siamo i responsabili della vita dell'oggetto referenziato.

Poiché i riferimenti strong sono quelli di default, non c'è necessità di mantenere un riferimento strong ad un oggetto: qualunque riferimento è un riferimento strong.

weak

Un riferimento weak in Swift indica che il riferimento punta ad un oggetto della cui vita non siamo noi i responsabili. È usato principalmente tra due oggetti che non necessitano l'uno dell'altro per mantenersi in vita.

C'è comunque un piccolo caveat. In Swift, i riferimenti weak devono sempre essere variabili con un tipo optional, perché essi sono settati a nil quando l'oggetto referenziato viene deallocato. La parola chiave weak è utilizzata per dichiarare una variabile come weak:

unowned

I riferimenti unowned sono nuovi ai programmatori Objective-C. Un riferimento unowned significa che non siamo noi i responsabili della vita dell'oggetto, come nel caso dei riferimenti weak.

La differenza con un riferimento weak è che un riferimento unowned non è settato a nil quando l'oggetto che referenzia viene deallocato. Un'altra importante differenza con i riferimenti weak è che i riferimenti unowned sono definiti con un tipo non optional.

I riferimenti unowned possono essere costanti. Un oggetto unowned non può esistere senza il suo proprietario e, pertanto, il riferimento unowned non è mai nil. I riferimenti unowned necessitano della parola chiave unowned prima della definizione della variabile o costante.

Conclusione

Swift è un linguaggio eccezionale che ha una notevole profondità e un grande potenziale. Scrivere programmi in Swift è divertente e rimuove parecchio codice boilerplate tipico dell'Objective-C, per fare in modo che il nostro codice sia sicuro.

Raccomando fortemente The Swift Programming Language, che è disponibile gratuitamente nell'Apple iBooks Store.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.