Compatibile con Xcode 7

Closure in Swift, un tipo particolare di funzione. Impariamo ad ordinare un array!

Le closure in Swift sono uno degli argomenti più ostici di un qualsiasi sviluppatore alle prime armi. Sono presenti anche in altri linguaggi di programmazione e, ad esempio, in Objective-C o Java dove prendono il nome di Block.

Dato che è abbastanza complesso spiegare a parole cos’è una Closure o un Block, anche se il linguaggio Swift le semplifica parecchio rispetto agli altri linguaggi, preferisco partire da un esempio.

Immagina d’avere la seguente funzione:

La miaFunzione si comporta così:

  • Accetta come unico parametro una funzione di tipo () -> (). Cioè che non ha nessun parametro in ingresso e nessuno in uscita.
  • Al suo interno si limita a chiamare la funzione passata come parametro ed a stampare un messaggio di corretta esecuzione.

Dalle lezioni sulle funzioni con il linguaggio Swift, sai che puoi chiamare la funzione in questa maniera:

Dopo aver creato una funzione di tipo () -> (), che si limita a scrivere un messaggio sulla console, l’ho passata al parametro della funzione eseguiUnaFunzione. L’output su console che dovresti vedere è:

Introduzione alle Closure in Swift

Ok, fin qui ci sapevo arrivare anche da solo. Cos’è una Closure in Swift e cosa c’entra con tutto questo?

Una Closure con il linguaggio Swift ti permette di definire il comportamento di un parametro, di tipo funzione, all’interno della chiamata di una qualsiasi funzione.

Riprendi sotto mano la funzione miaFunzione, se dovessimo utilizzare le closure, la sua chiamata diverrebbe così:

Invece di passare una funzione, si eseguono i seguenti passaggi:

  • Si scrive il nome della funzione da chiamare.
  • Si aprono le parentesi graffe.
  • Si scrive il tipo di dato della funzione passata al parametro.
  • Si fa seguire la parola chiave in.
  • Si scrive il comportamento della funzione che stai passando al parametro.
  • Si chiude la parentesi graffa.

Se adesso scrivi, dopo la parola chiave in, un qualsiasi codice, vedrai eseguire sia questo che quello definito dalla funzione eseguiUnaFunzione.

L’output della funzione diventa il seguente:

Se ti ricordi, nella definizione del corpo della funzione eseguiUnaFunzione, la prima riga di codice è la chiamata alla funzione passata al suo parametro.  Grazie alle Closure, invece di creare una seconda funzione (nell’esempio si chiamava salutoGenerico), puoi scrivere il comportamento della funzione che dovresti passare al parametro, direttamente nella chiamata della funzione.

Questo è un grande passo in avanti perché ti permette di circoscrivere il comportamento di una funzione tutto all’interno di un singolo blocco d’istruzioni (da questo ragionamento deriva il termine Block o Closure).

Closure in Swift, definizione e sintassi

Vediamo un altro esempio di Closure del linguaggio Swift. Ipotizziamo di creare una funzione che permette di salutare una persona:

La funzione è composta da due parametri. Il primo è il nome della persona da salutare ed il secondo è una funzione che genera una String in base alla String passata come parametro. All’interno la funzione stampa la String, restituita dalla funzione tipoSaluto (il parametro), a cui è stato passato il parametro nome.

Senza la closure avresti scritto una nuova funzione e poi l’avresti passata alla funzione:

Il risultato analogo è possibile eseguirlo con la Closure in questa maniera:

Nello spazio in cui viene scritto il tipo di dato della closure, cioè tra la parentesi graffa e la parola in, puoi assegnare un nome al tipo di dato in ingresso della closure. In questo modo catturi il valore passato al parametro, che nella definizione della funzione salutaPersona è il primo parametro (cioè il parametro nome) e lo concatena alla stringa “ciao”.

In questo modo hai modificato il comportamento della funzione tipoSaluto direttamente all’interno della funzione salutaPersona. Poi, quest’ultima, prenderà il valore che restituisce la closure e lo stamperà sulla console.

Closure in Swift

Uno dei principali punti di forza di una Closure con il linguaggio Swift è sicuramente quello di poter racchiudere una funzione complessa in un solo blocco di istruzioni. 

Anche se aumenta leggermente la complessità di scrittura, la nota positiva è che si evita di dover scrivere diverse funzioni che potrebbero facilmente confondersi all’interno di progetti più complessi. In questo modo, puoi cambiare senza grosse difficoltà il comportamento della funzione generale senza dover interagire sulle eventuali funzioni che comporrebbero il sistema.

Closure in Swift con parametri

Esempi di Closure con il linguaggio Swift. Ordinare un array e Mappare un dizionario

Molte delle funzioni che andrai ad analizzare ed utilizzare durante il corso di sviluppo applicazioni iOS con il linguaggio Swift sono, spesso e volentieri, delle closure.

Ti farò vedere due degli algoritmi più famosi:

  • sort: per l’ordinamento di un array.
  • map: per la mappatura di un dizionario.

La funzione sort, ordinare un array in maniera crescente o decrescente

Immagina di dover ordinare in modo inverso un array di stringhe:

In modo inverso vuol dire che l‘elemento più grande, che per le stringhe si intende quello con la lettera maggiore, viene inserito per primo.

Così, se in un’ordinazione normale hai in prima linea la lettera: A, in ordinazione inversa hai la lettera Z. Cosa accade quando una parola è composta da più caratteri?

Considera queste due stringhe:

Un carattere, all’interno del computer, non viene rappresentato da un simbolo (come lo è per l’essere umano) bensì viene rappresentato da un codice. Questo codice viene associato ad una tabella (la tabella ASCII) da cui viene recuperato il simbolo del rispettivo codice.

Non so se sono stato chiaro!

In questa maniera, quando deve confrontare i caratteri, non fa altro che confrontare il codice relativo e determinare se è uguale, maggiore o minore.

Perché A > B è False? Il problema risiede nel fatto che il codice di “A” è più piccolo rispetto a “B” se ci pensi un elenco lo ordini a partire da 0 fino ad arrivare a X:

Sulla base di questa logica un algoritmo di ordinamento non fa altro che confrontare carattere per carattere e determinare quale delle due stringhe ha i caratteri minori rispetto all’altra. Infatti se adesso confronti le due stringhe:

Ottieni che la stringa1 è ovviamente minore della stringa2 quindi viene prima della seconda.

Tornando al problema principale dell’ordinamento dell’array, seguendo questa logica, puoi procedere in 2 modi:

  • Creare un algoritmo ad hoc che passi il suo tempo a confrontare tutti gli elementi (sconsigliato).
  • Utilizzare una delle funzioni di sorting presenti nella Standard Library di Swift.

Il motivo per cui non ti faccio vedere l’algoritmo di ordinamento risiede nel fatto che richiederebbe un’intera lezione per spiegarti nel dettaglio il funzionamento. Oltre al funzionamento entrano in gioco variabili, come la complessità di un algoritmo, che per il momento non ho mai tirato in ballo.

Per complessità, in informatica, si intende il tempo che l’algoritmo impiega ad eseguire tutte le istruzioni. Ti prometto che ne parlerò in un articolo del blog dato che la complessità è uno dei miei campi preferiti :).

Il codice per l’ordinamento

La Swift’s Standard Library, come ti ho detto, offre la possibilità di utilizzare la funzione sort per ordinare un array.

La funzione sort, che puoi chiamare mettendo un punto dopo l’array, prevede come suo unico parametro d’ingresso una funzione. Questa funzione definisce in che modo dover ordinare l’array. La funzione in questione ritorna true quando il primo elemento deve trovarsi prima del secondo elemento.

La funzione ritorna un array ordinato, quindi non ordina l’array utilizzato per richiamare tale funzione, ma su quello ne crea uno con gli elementi ordinati.

Il primo esempio te lo faccio utilizzando il classico sistema delle funzioni:

Dato che volevo ordinare l’array in modo inverso, quindi con l’elemento maggiore in testa, ho impostato alla funzione di ritornare true quando la stringa1 è maggiore della stringa2. Se avessi voluto ordinare l’array in maniera normale mi sarebbe bastato cambiare il segno alla funzione.

Con le closure in Swift, sai che puoi eseguire tutta l’operazione in un’unica chiamata di funzione (e non con due funzioni come fatto prima). L’esempio sotto mostra l’utilizzo di una Closure nella funzione sort che hai utilizzato prima:

Decisamente, con le Closure, molto più semplice e veloce del sistema a due funzioni.

Esempio Closure in Swift funzione sort

Sintassi abbreviata di una Closure in Swift

Un altro modo per rendere ancora più compatta una closure è il seguente:

Cos’è sta roba? Procediamo per passi. Per ora sai 2 cose:

  • Il parametro della funzione sort, la closure, utilizza come parametri in ingresso due Stringhe e in uscita un Bool.
  • Nel paragrafo sopra hai scoperto che il tipo, per una closure, può essere omesso perché viene dedotto dal contesto in cui si trova.

Adesso grazie all’utilizzo di questa sintassi $0$1$2 e così via, sei in grado di compattare l’intera closure in un’unica espressione, quella di ritorno. Il $0 e $1 prendono il valore rispettivamente della prima e della seconda stringa e contemporaneamente a questo processo eseguono l’operazione di uscita.

Mappare gli elementi di un array

Utilizziamo un’altra funzione per gli array, la map. La funzione map permette di associare ad ogni elemento di un array uno o più valori di un dizionario.

La funzione prende in ingresso come unico parametro una closure dove viene stabilito in che modo mappare l’array.

Immagina di avere un array di interi: “var arrayInt = [10, 23, 33]” e di voler per mappare ogni elemento dell’array con i rispettivi valori in stringhe dei numeri che compongono ogni valore. Ad esempio per il numero 10 mappare significa ottenere una stringa con valore “UnoZero”.

In pratica la map prende il valore da un dizionario e lo associa all’elemento dell’array. Se l’elemento è composto da più elementi allora comporrà più valori insieme.

  • (var number) -> String, ad ogni numero passato la closure tornerà una stringa.
  • Nel whilenumber % 10 esegue la divisione tra il number e 10, se number è divisibile per 10 allora restituisce la sua parte decimale (per 10 torna 0, per 23 torna 3, e per 34 torna 4).  Così aggiunge a output la stringa corrispondente al valore della chiave ritornata da number % 10.
  • number /= 10 è l’equivalente di: number = number/10. In questo modo del numero 10 ora considererà 1, di 23 il 2 e di 34 il 3. Così facendo vengono mappati tutti i numeri che compongono un numero decimale.
  • Quando il while termina, quindi ha mappato tutti i valori del numero, restituisce la stringa.

Il while viene eseguito N volte quante sono le parti che compongono un numero. Se x = 426 l’algoritmo eseguirà 3 volte il passaggio di mappatura, al primo giro avrà mappato il 6, al sencondo il 2 e al terzo il 4. A fine ciclo l’output sarà uguale a QuattroDueSei.

Closure Map in swift

Considerazioni

Con le closure, però, sei in grado di compattare la la sintassi di una funzione senza dover cercare tutti gli altri pezzi che la compongono. Immagina, un giorno, di dover tornare a leggere il codice che hai scritto, un conto sarà cercare tutte le funzioni che compongono un’altra funzione, un altro è avere tutto a portata di mano.

Ora dovresti essere in grado di dire che una funzione è una particolare forma di closure, dato che le closure non sono altro che funzioni passate e elaborate all’interno di un parametro di un’altra funzione.

So che avrai capito ben poco, per assimilare il concetto di closure in Swift ci vuole un po’ di tempo e sicuramente molta pratica. Durante le varie lezioni del corso successivo e durante i tutorial sul blog ne capirai meglio l’utilizzo.

Buona Programmazione!

Torna a: Corso gratuito linguaggio di programmazione Swift > Dettagli e sfumature del linguaggio Swift

Start typing and press Enter to search

Le strutture con il linguaggio Swift