Advertisement
  1. Code
  2. Swift

iOS da Zero con Swift: Ancora Swift in Breve

Scroll to top
Read Time: 15 min
This post is part of a series called iOS From Scratch With Swift.
iOS From Scratch With Swift: Swift in a Nutshell
iOS From Scratch With Swift: Exploring the iOS SDK

Italian (Italiano) translation by Luca Menozzi (you can also view the original English article)

Anche se questo tutorial si concentra principalmente sulle funzioni e sulle closure, faremo una breve deviazione al termine per esplorare i protocolli e il controllo di accesso. Lavoreremo con i protocolli più avanti nella serie, per cui è importante prendere confidenza con essi il prima possibile.

Un po' di mesi fa, ho scritto una serie di articoli sul linguaggio di programmazione Swift nel quale do uno sguardo da vicino alle funzioni e alle closure. Questo articolo riassume ciò che ho scritto in quei tutorial. Se vuoi più informazioni su funzioni e closure, ti suggerisco di leggere gli articoli di quella serie:

Prima di buttarci sull'argomento, è venuto il momento di lanciare Xcode e di creare un nuovo playground. Pronto? Scriviamo un po' di codice.

1. Funzioni

Cos'è una Funzione

Una funzione non è altro che un blocco di codice che può essere eseguito all'occorrenza. Mi piacerebbe partire da un esempio per parlare dell'anatomia base di una funzione in Swift. Aggiungi la seguente definizione di funzione al tuo playground.

1
func printHelloWorld() {
2
    print("Hello World!")
3
}

Una funzione inizia con la parola chiave func ed è seguita dal nome della funzione, printHelloWorld nell'esempio precedente. Come in molti altri linguaggi, il nome della funzione è seguito da un paio di parentesi tonde che contengono i parametri della funzione, l'input della funzione.

Il corpo della funzione è racchiuso da un paio di parentesi graffe. Il corpo di printHelloWorld() contiene un'istruzione con la quale stampiamo a video "Hello World!" nell'output standard. Ecco com'è una funzione base in Swift. Per richiamare la funzione, scriviamo il suo nome seguito da un paio di parentesi tonde.

1
printHelloWorld()

Parametri

Rendiamo l'esempio precedente un po' più complesso aggiungendo dei parametri alla definizione della funzione. Con la loro aggiunta, forniamo alla funzione valori in input che può utilizzare al suo interno. Nell'esempio seguente, definiamo la funzione printMessage(_:) che accetta un parametro, message, di tipo String.

1
func printMessage(message: String) {
2
    print(message)
3
}

Una funzione può accettare più parametri o valori in input. I parametri sono racchiusi da parentesi tonde che seguono il nome della funzione. Il nome del parametro è seguito da due punti e dal suo tipo. Come ricorderai, questo è molto simile alla dichiarazione di una variabile o di una costante. Semplicemente dice che il parametro message è di tipo String.

Richiamare la funzione è simile a come abbiamo visto in precedenza. La differenza sta nel fatto che passiamo un argomento.

1
printMessage("Hello World!")

L' esempio che segue è simile. L'unica differenza è che la funzione definisce due parametri: message, di tipo String e times, di tipo Int.

1
func printMessage(message: String, times: Int) {
2
    for i in 0..<times {
3
        print("\(i) \(message)")
4
    }
5
}

Mentre il nome della funzione è identica a quella della funzione printMessage(_:) originale, il tipo di funzione è differente. E' importante che tu colga questa differenza. Ogni funzione ha un tipo, derivante dai tipi del parametro e dal tipo di ritorno. Esploreremo i tipi di ritorno tra un attimo. Le funzioni possono avere lo stesso nome purché il loro tipo sia differente, come mostrato nelle due precedenti definizioni di funzione.

Il tipo della prima funzione è (String) -> () mentre il tipo della seconda è (String, Int) -> (). Il nome di entrambe è lo stesso. Non ti preoccupare per il simbolo ->. Il suo significato diverrà chiaro tra poco, quando parleremo dei tipi di ritorno.

La seconda funzione printMessage(_:times:) definisce due parametri: message, di tipo String e times, di tipo Int. La firma di questa funzione mostra una delle caratteristiche che Swift ha adottato da Objective-C: la funzione comprensibile e i nomi del metodo.

1
printMessage("Hello World!", times: 3)

Tipi di Ritorno

Le funzioni che abbiamo visto finora non restituiscono nulla quando le richiamiamo. Creiamo una funzione per formattare una data. La funzione prende due argomenti: una data, di tipo NSDate e una stringa di formattazione, di tipo String. Dato che le classi NSData e NSDateFormatter sono definite nel framework Foundation, dobbiamo importarlo all'inizio del playground.

1
import Foundation
2
3
func formatDate(date: NSDate, format: String = "YY/MM/dd") -> String {
4
    let dateFormatter = NSDateFormatter()
5
    dateFormatter.dateFormat = format
6
    return dateFormatter.stringFromDate(date)
7
}

Ci sono un po' di cose che richiedono spiegazioni. Il simbolo -> indica che la funzione restituisce un valore. Il tipo del valore è definito dopo il simbolo ->, String.

La funzione accetta due argomenti e il secondo ha un valore di default. Questo è indicato dall'assegnazione dopo il tipo del secondo argomento. Il valore di default di format è "YY/MM/dd". Questo significa che possiamo richiamare la funzione anche con un solo argomento. Questa cosa la si vede anche dall'autocompletamento di Xcode.

Default Value for ParameterDefault Value for ParameterDefault Value for Parameter

Se una funzione non ha un tipo di ritorno, il simbolo -> viene omesso. Ecco perché la funzione printHelloWorld() non ha il simbolo -> nella definizione del suo metodo.

Nomi di Parametri Locali ed Esterni.

In precedenza, in questo tutorial, abbiamo definito la funzione printMessage(_:). Anche se abbiamo dato un nome al parametro, message, non usiamo quel nome quando richiamiamo la funzione. Invece, passiamo solo il valore per il parametro message.

1
func printMessage(message: String) {
2
    print(message)
3
}
4
5
printMessage("Hello World!")

Il nome che definiamo nella definizione di funzione è un nome di parametro locale. Il valore del parametro può essere richiamato tramite il suo nome nel corpo della funzione. Ma i parametri della funzione hanno un po' di flessibilità in più.

Objective-C è famoso per i suoi nomi dei metodi lunghi. Anche se ciò sembra goffo e inelegante a coloro che non conoscono questo linguaggio, rende i metodi facili da comprendere e, se scelti bene, molto descrittivi. Se pensi di aver perso questa caratteristica passando a Swift, allora stai per scoprire che è vero il contrario.

Quando una funzione accetta diversi parametri, non è sempre scontato quale argomento corrisponde a quale parametro. Dai un'occhiata all'esempio seguente per capire meglio il problema.

1
func power(a: Int, b: Int) -> Int {
2
    var result = a
3
    
4
    for _ in 1..<b {
5
        result = result * a
6
    }
7
    
8
    return result
9
}
10
11
power(2, 3)

La funzione power(_:b:) eleva a potenza il valore di a all'esponente b. Entrambi i parametri sono di tipo Int. Mentre la maggior parte delle persone passerebbe intuitivamente il valore della base come primo argomento e l'esponente come secondo, questo non è chiaro né dal tipo, né dal nome o dalla firma della funzione.

Per evitare confusione, possiamo dare ai parametri di una funzione nomi esterni. Possiamo quindi usare questi nomi esterni quando la funzione viene richiamata per indicare in maniera disambigua quale argomento corrisponde a quale parametro. Dai un'occhiata al seguente esempio aggiornato.

1
func power(base a: Int, exponent b: Int) -> Int {
2
    var result = a
3
    
4
    for _ in 1..<b {
5
        result = result * a
6
    }
7
    
8
    return result
9
}

Osserva che il corpo della funzione non è cambiato, perché i nomi locali non sono stati modificati. Tuttavia, quando richiamiamo la funziona aggiornata, la differenza è evidente e il risultato molto meno confuso.

1
power(base: 2, exponent: 3)

Anche se i tipi di entrambe le funzioni sono gli stessi, (Int, Int) -> Int, le funzioni sono differenti. In altre parole, la seconda funzione non è una ridichiarazione della prima. La sintassi per richiamare la seconda funzione può ricordare l'objective-C ad alcuni di voi. Non solo gli argomenti sono chiaramente descritti, ma la combinazione di funzione e nomi di parametri descrivono lo scopo della funzione.

In Swift, il primo parametro non ha, di default, nessun nome di parametro esterno. Ecco perché la firma della funzione printMessage(_:) include un _ per il primo parametro. Se vogliamo definire un nome di parametro esterno per il primo parametro, allora la definizione del metodo apparirebbe un po' diversa.

1
func printMessage(message message: String) {
2
    print(message)
3
}
4
5
printMessage(message: "Hello World!")

Funzioni Globali e Annidate

Gli esempi che abbiamo visto finora sono noti come funzioni globali, perché sono definiti in un ambito globale. Le funzioni possono anche essere annidate. Le funzioni annidate possono essere richiamate solo nell'ambito nel quale sono definite. Ciò significa che una funzione annidata può essere richiamata solo dalla funzione nella quale viene definita. Il prossimo esempio chiarisce questo concetto.

1
func printMessage(message: String) {
2
    let a = "hello world"
3
    
4
    func printHelloWorld() {
5
        print(a)
6
    }
7
}

2. Closure

Cos'è una Closure?

Se hai lavorato con i blocchi in C e Objective-C o le closure in JavaScript, allora non dovresti sbattere la testa contro il muro per capire cosa sono le closure. Abbiamo già lavorato con le closure in questo articolo, perché le funzioni sono anch'esse closure. Partiamo con le basi e analizziamo l'anatomia di una closure.

Una closure è un blocco di funzionalità che puoi distribuire nel tuo codice. Puoi passare una closure come argomento di una funzione o puoi memorizzarla come proprietà di un oggetto. Le closure hanno molti casi d'uso.

Il nome closure suggerisce una delle loro caratteristiche chiave. Una closure prende le variabili e le costanti dal contesto nel quale è definita. Questo è definito talvolta come closing over.

Sintassi

La sintassi base di una closure non è complicata. Dai un'occhiata al seguente esempio.

1
let a = {(a: Int) -> Int in
2
    return a + 1
3
}

La prima cosa che noterai è che l'intera closure è racchiusa da un paio di parentesi graffe. I parametri della closure sono racchiusi da un paio di parentesi tonde, separati dal tipo di ritorno tramite il simbolo ->. La closure precedente accetta un argomento, a, di tipo Int e restituisce un Int. Il corpo inizia dopo la parola chiave in.

Le closure con un nome, come le funzioni globali e quelle annidate, hanno un aspetto un po' differente. L'esempio seguente mostra queste differenze.

1
func increment(a: Int) -> Int {
2
    return a + 1
3
}

Le differenze più evidenti stanno nell'utilizzo della parola chiave func e nella posizione dei parametri e del tipo di ritorno. Una closure inizia e termina con le parentesi graffe, e racchiude i parametri, il tipo di ritorno e il corpo. Nonostante le differenze, ricorda che ogni funzione è una closure. Ma non sempre una closure è una funzione.

Closure e Parametri

Le closure sono potenti e l'esempio seguente mostra quanto utili possano essere. Nell'esempio, creiamo un array di stati. Richiamiamo la funzione map() sull'array per crearne uno nuovo che contenga soltanto le prime due lettere, in maiuscolo, di ogni stato.

1
var states = ["California", "New York", "Texas", "Alaska"]
2
3
let abbreviatedStates = states.map({ (state: String) -> String in
4
    let index = state.startIndex.advancedBy(2)
5
    return state.substringToIndex(index).uppercaseString
6
})
7
8
print(abbreviatedStates)

Nell'esempio precedente, la funzione map() è richiamata sull'array states, trasforma il suo contenuto e restituisce un nuovo array che contiene i valori trasformati. L'esempio mostra anche la potenza della deduzione del tipo in Swift. Dato che richiamiamo la funzione map() su un array di stringhe, Swift deduce che l'argomento state è di tipo String. Questo significa che possiamo omettere il tipo, come mostrato nell'esempio aggiornato qui di seguito.

1
let abbreviations = states.map({ (state) -> String in
2
    return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString
3
})

Ci sono alcune altre cose che possiamo omettere nell'esempio precedente, come si può vedere nella seguente riga singola.

1
let abbreviations = states.map({ state in state.substringToIndex(state.startIndex.advancedBy(2)).uppercaseString })

Lascia che ti spieghi cosa succede. Il compilatore può dedurre che restituiamo una stringa dalla closure che passiamo alla funzione map(), il che significa che non c'è ragione per includere il tipo di ritorno nella definizione dell'espressione della closure. Possiamo, però, fare questo solo se il corpo della closure include una singola istruzione. In questo caso, possiamo inserire quella istruzione sulla stessa riga della definizione della closure, come mostrato nell'esempio precedente. Dato che non c'è un tipo di ritorno nella definizione e nessun simbolo -> che precede il tipo di ritorno, possiamo omettere le parentesi che racchiudono i parametri della closure.

Abbreviazione dei Nomi degli Argomenti

E non è tutto. Possiamo usare l'abbreviazione dei nomi degli argomenti per semplificare ancora di più la closure precedente. Quando si usa un'espressione closure di una sola riga, come quella dell'esempio precedente, possiamo omettere la lista di parametri, inclusa la parola chiave in che separa i parametri dal corpo della closure.

Nel corpo della closure, richiamiamo gli argomenti usando delle abbreviazioni dei loro nomi che ci fornisce Swift. Il primo argomento viene richiamato con $0, il secondo con $1, e così via.

Nell'esempio aggiornato qui di seguito, ho omesso la lista di parametri e la parola chiave in, e ho sostituito l'argomento state, nel corpo della closure, con l'abbreviazione $0. L'istruzione risultante è più concisa ma sempre molto leggibile.

1
let abbreviations = states.map({ $0.substringToIndex($0.startIndex.advancedBy(2)).uppercaseString })

Trailing Closure

Il linguaggio Swift definisce anche un concetto conosciuto come trailing closures. Se passi una closure come ultimo argomento di una funzione, puoi mettere quella closure fuori dalle parentesi della chiamata alla funzione. Il seguente esempio dimostra come funziona.

1
let abbreviations = states.map() { $0.substringToIndex($0.startIndex.advancedBy(2)).uppercaseString }

Se il solo argomento della chiamata della funzione è la closure, allora è addirittura possibile omettere le parentesi della chiamata della funzione.

1
let abbreviations = states.map { $0.substringToIndex($0.startIndex.advancedBy(2)).uppercaseString }

Nota che questo funziona anche per le closure che contengono più istruzioni. Infatti, questa è la principale ragione per cui le trailing closure sono disponibili in Swift. Se una closure è lunga e complessa ed è l'ultimo argomento di una funzione, è spesso meglio utilizzare la sintassi delle trailing closure.

1
let abbreviations = states.map { (state: String) -> String in
2
    let toIndex = state.startIndex.advancedBy(2)
3
    let newState = state.substringToIndex(toIndex)
4
    return newState.uppercaseString
5
}

3. Protocolli

I protocolli sono un componente importante di Swift. Il concetto non è difficile da comprendere se hai familiarità con i protocolli in Objective-C o le interfacce in Java. Un protocollo definisce un design o un'interfaccia specializzata in un particolare compito. Lo fa definendo proprietà e metodi necessari per eseguire quel compito.

Definire un protocollo sembra simile a definire una classe o una struttura. Nell'esempio seguente, definiamo il protocollo Repairable. Il protocollo definisce due proprietà, time e cost, e il metodo repair(). La proprietà time è in sola lettura, mentre cost è sia in lettura che in scrittura.

1
protocol Repairable {
2
    var time: Float { get }
3
    var cost: Float { get set }
4
    
5
    func repair()
6
}

Una classe o struttura può adottare un protocollo conformandosi ad esso. Questo vuol dire che è richiesto per implementare le proprietà e i metodi definiti nel protocollo. Aggiorniamo la classe Boat che abbiamo implementato nel precedente tutorial.

1
class Boat: Repairable {
2
    
3
    var speed: Float = 0
4
    var lifeboats: Int = 2
5
    
6
    var time: Float = 10.0
7
    var cost: Float = 100.0
8
    
9
    func deployLifeboats() {
10
        // ...

11
    }
12
    
13
    func repair() {
14
        // ...

15
    }
16
    
17
}

Ecco com'è facile conformare un tipo ad un protocollo. Sebbene un protocollo non possa essere istanziato come una classe o una struttura, rimane un tipo valido. Dai un'occhiata all'esempio seguente nel quale il protocollo Repairable è usato come tipo di un argomento di funzione.

1
func bringInForRepairs(toBeRepaired: Repairable) {
2
    // ...

3
}
4
5
let myDamagedBoat = Boat()
6
7
bringInForRepairs(myDamagedBoat)

Di recente, Davis Allie, ha scritto un ottimo articolo sulla programmazione orientata al protocollo in Swift. Se ti interessa imparare di più sui protocolli e sul loro potenziale in Swift, ti suggerisco di leggere l'articolo di Davis.

4. Controllo di Accesso

Mi piacerebbe terminare questa introduzione a Swift parlando del controllo di accesso. Come suggerisce il nome, il controllo di accesso definisce quale codice ha accesso a quale codice. I livelli di controllo di accesso si applicano ai metodi, alle funzioni, ai tipi, etc. Apple semplicemente si riferisce alle entità. Esistono tre livelli di controllo di accesso: public, internal e private.

  • Public: le entità segnate come public sono accessibili sia da entità definite nello stesso modulo, sia da altri moduli, come un progetto, un framework o una libreria. Questo livello di controllo di accesso è ideale per esporre l'interfaccia di un framework.
  • Internal: é il livello di controllo di accesso di default. In altre parole, se non è specificato nessun livello di controllo di accesso, viene applicato il livello internal. Un'entità con livello internal è accessibile solamente dalle entità definite nello stesso modulo.
  • Private: un'entità dichiarata come private è accessibile solamente da entità definite nello stesso file sorgente.

Dai un'occhiata all'esempio seguente nel quale ho aggiornato la classe Boat. La classe stessa è segnata come public, accessibile quindi dall'esterno. La proprietà cost è implicitamente segnata come internal, perché non abbiamo specificato un livello di controllo di accesso. Lo stesso vale per il metodo deployLifeboats().

1
public class Boat {
2
    
3
    public var speed: Float = 0
4
    public var lifeboats: Int = 2
5
    
6
    var cost: UInt = 100
7
    
8
    func deployLifeboats() {
9
        // ...

10
    }
11
    
12
    private func scheduleMaintenance() {
13
        
14
    }
15
}

Il metodo scheduleMaintenance() è segnato come private, ovvero può essere richiamato solo da entità che si trovano nel file sorgente nel quale la classe Boat è definita. E' importante capire questa cosa, perché è leggermente differente da quello che gli altri linguaggi di programmazione considerano un metodo o proprietà privati.

Se segniamo la classe Boat come internal, rimuovendo la parola chiave public, il compilatore ci mostrerà un avviso. Ci dice che non possiamo segnare speed e lifeboats come public se Boat è segnato come internal. Il compilatore ha ovviamente ragione. Non ha senso segnare le proprietà di una classe internal come public.

Access Control WarningAccess Control WarningAccess Control Warning

Conclusioni

Il linguaggio Swift è facile da imparare, ma c'è molto di più rispetto a quello che abbiamo trattato nei due tutorial precedenti. Apprenderai di più su Swift una volta che inizieremo a costruire applicazioni. Nel prossimo articolo, daremo un'occhiata più da vicino all'SDK di iOS.

Per qualsiasi domanda o commento, puoi lasciarli qui di seguito o sul mio account Twitter.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
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.