Swift From Scratch chiusure

Se hai lavorato con blocchi in C o Objective-C o lambda in Ruby, allora non avrai difficoltà a comprendere il concetto di chiusure. Le chiusure non sono altro che blocchi di funzionalità che è possibile aggirare nel codice.

In effetti, abbiamo già lavorato con chiusure nelle lezioni precedenti. Esatto: anche le funzioni sono chiusure. Cominciamo con le basi e ispezioniamo l'anatomia di una chiusura.

1. Che cos'è una chiusura?

Come ho detto, una chiusura è un blocco di funzionalità che è possibile aggirare nel codice. Puoi passare una chiusura come argomento di una funzione o puoi memorizzarla come una proprietà di un oggetto. Le chiusure hanno molti casi d'uso.

Il nome chiusura suggerisce una delle caratteristiche chiave delle chiusure. Una chiusura cattura le variabili e le costanti del contesto in cui è definito. A volte ci si riferisce a come chiusura quelle variabili e costanti. Vedremo il valore catturare in modo più dettagliato alla fine di questa lezione.

Flessibilità

Hai già imparato che le funzioni possono essere incredibilmente potenti e flessibili. Poiché le funzioni sono chiusure, le chiusure sono altrettanto flessibili. In questo articolo, scopri quanto sono flessibili e potenti.

Gestione della memoria

Il linguaggio di programmazione C ha un concetto simile, blocchi. Le chiusure in Swift, tuttavia, hanno alcuni vantaggi. Uno dei principali vantaggi delle chiusure in Swift è che la gestione della memoria è qualcosa di cui tu, lo sviluppatore, non devi preoccuparti.

Anche i cicli di mantenimento, che non sono rari in C o Objective-C, sono gestiti da Swift. Questo riduce perdite di memoria difficili da trovare o arresti anomali causati da puntatori non validi.

2. Sintassi

La sintassi di base di una chiusura non è difficile e potrebbe ricordarti funzioni globali e annidate, che abbiamo trattato in precedenza in questa serie. Dai un'occhiata al seguente esempio.

(a: Int) -> Int in ritorno a + 1

La prima cosa che noti è che l'intera chiusura è avvolta in un paio di parentesi graffe. I parametri della chiusura sono racchiusi in una coppia di parentesi, separate dal tipo restituito dal -> simbolo. La chiusura sopra accetta un argomento, un, di tipo Int, e restituisce un Int. Il corpo della chiusura inizia dopo il nel parola chiave.

Le chiusure con nomi, ovvero funzioni globali e annidate, hanno un aspetto leggermente diverso. Il seguente esempio dovrebbe illustrare le differenze.

func increment (_ a: Int) -> Int return a + 1

Le differenze più importanti sono l'uso del func parola chiave e la posizione dei parametri e il tipo di ritorno. Una chiusura inizia e termina con una parentesi graffa, avvolgendo i parametri, il tipo di ritorno e il corpo di chiusura. Nonostante queste differenze, ricorda che ogni funzione è una chiusura. Tuttavia, non tutte le chiusure sono una funzione.

3. Chiusure come parametri

Le chiusure sono potenti e il seguente esempio illustra quanto possano essere utili. Nell'esempio, creiamo una serie di stati. Invochiamo il carta geografica(_:) funzione sulla matrice per creare una nuova matrice che contiene solo le prime due lettere di ogni stato come una stringa in maiuscolo.

var states = ["California", "New York", "Texas", "Alaska"] let abbreviatedStates = states.map ((state: String) -> String in let index = state.index (state.startIndex, offsetBy : 2) return state.substring (to: index) .uppercased ()) print (abbreviatedStates)

Il carta geografica(_:) funzione o metodo è comune a molti linguaggi di programmazione e librerie, come Ruby, PHP e JavaScript. Nell'esempio sopra, il carta geografica(_:) la funzione è invocata su stati array, trasforma i suoi contenuti e restituisce un nuovo array che contiene i valori trasformati. Non preoccuparti per il corpo della chiusura, per ora.

Tipo di inferenza

Precedentemente in questa serie, abbiamo appreso che Swift è piuttosto intelligente. Lascia che ti mostri esattamente quanto è intelligente. La matrice di stati è una serie di stringhe. Perché invochiamo il carta geografica(_:) funzione sull'array, Swift sa che il stato l'argomento è di tipo Stringa. Ciò significa che possiamo omettere il tipo, come mostrato nell'esempio aggiornato di seguito.

let abbreviatedStates = states.map ((state) -> String in let index = state.index (state.startIndex, offsetBy: 2) return state.substring (to: index) .uppercased ())

Ci sono alcune altre cose che possiamo omettere dall'esempio sopra, risultante nella seguente copertina.

let abbreviatedStates = states.map (state in state.substring (to: state.index (state.startIndex, offsetBy: 2)). uppercased ())

Lascia che ti spieghi cosa sta succedendo. 

Il compilatore può dedurre che restituiamo una stringa dalla chiusura che passiamo al carta geografica(_:) funzione, il che significa che non vi è alcun motivo per includerlo nella definizione dell'espressione di chiusura. 

Possiamo farlo solo se il corpo della chiusura include una singola affermazione, però. In tal caso, possiamo mettere questa affermazione sulla stessa linea della definizione della chiusura, come mostrato nell'esempio precedente. Perché non c'è nessun tipo di ritorno nella definizione e no -> simbolo che precede il tipo di ritorno, possiamo omettere le parentesi che racchiudono i parametri della chiusura.

Nomi di argomenti abbreviati

Non si ferma qui, però. Possiamo utilizzare nomi di argomenti abbreviati per semplificare ulteriormente l'espressione di chiusura sopra descritta. Quando si utilizza un'espressione di chiusura in linea, come nell'esempio precedente, è possibile omettere l'elenco di parametri, incluso il nel parola chiave che separa i parametri dal corpo di chiusura.

Nel corpo di chiusura, facciamo riferimento agli argomenti usando i nomi degli argomenti abbreviati di cui ci fornisce Swift. Il primo argomento è citato da $ 0, il secondo di $ 1, eccetera.

Nell'esempio aggiornato di seguito, ho omesso l'elenco dei parametri e il nel parola chiave e sostituito il stato argomento nel corpo della chiusura con il nome dell'argomento abbreviato $ 0. La dichiarazione risultante è più concisa senza compromettere la leggibilità.

let abbreviatedStates = states.map ($ 0.substring (to: $ 0.index ($ 0.startIndex, offsetBy: 2)). uppercased ())

Chiusure finali

Il linguaggio di programmazione Swift definisce anche un concetto noto come chiusure finali. L'idea è semplice. Se si passa una chiusura come ultimo argomento di una funzione, è possibile posizionare tale chiusura al di fuori delle parentesi della chiamata di funzione. Il seguente esempio dimostra come funziona.

let abbreviatedStates = states.map () $ 0.sottstring (to: $ 0.index ($ 0.startIndex, offsetBy: 2)). uppercased ()

Se l'unico argomento della chiamata di funzione è la chiusura, allora è anche possibile omettere le parentesi della chiamata di funzione.

let abbreviatedStates = states.map $ 0.substring (to: $ 0.index ($ 0.startIndex, offsetBy: 2)). uppercased ()

Si noti che questo funziona anche per chiusure che contengono più istruzioni. In realtà, questo è il motivo principale per cui le chiusure finali sono disponibili in Swift. Se una chiusura è lunga o complessa ed è l'ultimo argomento di una funzione, è spesso preferibile utilizzare la sintassi della chiusura finale.

let abbreviatedStates = states.map (state) -> String in let index = state.index (state.startIndex, offsetBy: 2) return state.substring (to: index) .uppercased ()

4. Cattura dei valori

Quando usi le chiusure, ti troverai spesso a utilizzare o manipolare costanti e variabili dal contesto circostante della chiusura nel corpo della chiusura. Questo è spesso definito come acquisizione di valore. Significa semplicemente che una chiusura può catturare i valori di costanti e variabili dal contesto in cui è definita. Prendi il seguente esempio per comprendere meglio il concetto di acquisizione del valore.

func changeCase (maiuscolo: Bool, ofStrings stringhe: String ...) -> [String] var newStrings = [String] () func changeToUppercase () per s nelle stringhe newStrings.append (s.uppercased ()) func changeToLowerCase () for s in string newStrings.append (s.lowercased ()) se maiuscolo changeToUppercase () else changeToLowerCase () return newStrings lascia uppercasedStates = changeCase (uppercase: true, ofStrings: "California "," New York ") lasciate lowercasedStates = changeCase (uppercase: false, ofStrings:" California "," New York ")

Sono sicuro che sei d'accordo che l'esempio sopra riportato sia un po 'forzato, ma mostra chiaramente come funziona la cattura dei valori in Swift. Le funzioni annidate, changeToUppercase () e changeToLowercase (), avere accesso agli argomenti della funzione esterna, stati, così come il newStates variabile dichiarata nella funzione esterna. 

Lascia che ti spieghi cosa succede.

Il changeCase (maiuscole: ofStrings :) la funzione accetta un valore booleano come primo argomento e un parametro variadico di tipo Stringa come il suo secondo parametro. La funzione restituisce una serie di stringhe composte dalle stringhe passate alla funzione come secondo argomento. Nel corpo della funzione, creiamo un array mutevole di stringhe, newStrings, in cui memorizziamo le stringhe modificate.

Le funzioni nidificate si sovrappongono alle stringhe passate a changeCase (maiuscole: ofStrings :) funzione e cambia il caso di ogni stringa. Come puoi vedere, hanno accesso diretto alle stringhe passate a changeCase (maiuscole: ofStrings :) funzione così come il newStrings array, che è dichiarato nel corpo del changeCase (maiuscole: ofStrings :) funzione.

Controlliamo il valore di lettere maiuscole, chiamare la funzione appropriata e restituire il newStrings array. Le due righe alla fine dell'esempio dimostrano come changeCase (maiuscole: ofStrings :) la funzione funziona.

Anche se ho dimostrato di acquisire valore con le funzioni, ricorda che ogni funzione è una chiusura. In altre parole, le stesse regole si applicano alle chiusure senza nome.

chiusure

È stato menzionato più volte in questo articolo: le funzioni sono chiusure. Esistono tre tipi di chiusure:

  • funzioni globali
  • funzioni annidate
  • espressioni di chiusura

Funzioni globali, come ad esempio stampa (_: separatore: Terminator :) funzione della libreria standard Swift, non acquisire valori. Le funzioni annidate, tuttavia, hanno accesso e possono acquisire i valori di costanti e valori della funzione in cui sono definiti. L'esempio precedente illustra questo concetto.

Le espressioni di chiusura, note anche come chiusure senza nome, possono acquisire i valori delle costanti e delle variabili del contesto in cui sono definite. Ciò è molto simile alle funzioni annidate.

Copia e riferimento

Una chiusura che cattura il valore di una variabile è in grado di cambiare il valore di quella variabile. Swift è abbastanza intelligente da sapere se deve copiare o fare riferimento ai valori delle costanti e delle variabili che cattura.

Gli sviluppatori che apprendono Swift e hanno poca esperienza con altri linguaggi di programmazione assumeranno questo comportamento per scontato. Tuttavia, è un vantaggio importante che Swift capisca come vengono utilizzati i valori catturati in una chiusura e, di conseguenza, può gestire la gestione della memoria per noi.

Conclusione

Le chiusure sono un concetto importante e le userai spesso in Swift. Ti permettono di scrivere un codice flessibile e dinamico che sia facile da scrivere e da capire. 

Nel prossimo articolo esploreremo la programmazione orientata agli oggetti in Swift, iniziando con oggetti, strutture e classi.

Se vuoi imparare come utilizzare Swift 3 per codificare le funzioni avanzate per le app del mondo reale, consulta il nostro corso Vai oltre Con Swift: Animazione, Rete e Controlli personalizzati. Segui Markus Mühlberger mentre codifica un'app meteo iOS funzionale con dati meteo in tempo reale, componenti dell'interfaccia utente personalizzati e alcune animazioni slick per dare vita a tutto.