Il linguaggio moderno di Apple per sviluppare app iOS e macOS: sicuro, veloce ed espressivo. Impara variabili, Optional, closures, struct, classi, protocolli e gestione degli errori. Con esempi pratici ed esercizi.
Introduzione a Swift
Swift è il linguaggio di programmazione moderno sviluppato da Apple nel 2014 per costruire app per iOS, macOS, watchOS e tvOS. È potente, sicuro, espressivo e veloce, e ha sostituito Objective-C come linguaggio principale dell'ecosistema Apple.
Caratteristica
Descrizione
Sicuro
Optional e type safety eliminano molti crash a runtime
Veloce
Compilato e ottimizzato, performance vicine al C
Moderno
Sintassi pulita, inferenza di tipo, closures, generics
Open source
Disponibile anche su Linux e Windows
SwiftUI
Framework dichiarativo per costruire interfacce
Con Swift puoi creare app per iPhone e iPad, applicazioni desktop per Mac, e persino servizi backend lato server.
💡 Questo corso copre i fondamenti del linguaggio Swift: la base indispensabile prima di affrontare SwiftUI e lo sviluppo di app iOS complete.
Installazione e primi passi
Per programmare in Swift hai diverse opzioni a seconda del tuo sistema operativo.
Piattaforma
Strumento
macOS
Xcode (gratuito su Mac App Store) — IDE completo
iPad
Swift Playgrounds — app per imparare interattivamente
Linux / Windows
Swift toolchain da swift.org
Browser
Swift Playground online — nessuna installazione
Verifica installazione e REPL
# Verifica la versione installata
swift --version
# Avvia il REPL interattivo
swift
# Esegui un file .swift
swift main.swift
Il tuo primo programma Swift
// main.swift
print("Ciao, mondo!")
let nome = "Alice"
print("Benvenuta, \(nome)!") // interpolazione di stringa
// Niente punto e virgola necessario a fine riga
💡 In Swift il punto e virgola a fine riga è opzionale. L'interpolazione con \(...) inserisce valori dentro le stringhe.
Sintassi base
Struttura e output
// Commento su una riga
/* Commento
su più righe */
// Output con print
print("Riga 1")
print("Riga 2")
// print accetta più argomenti
print("a", "b", "c") // a b c
print("x", "y", separator: "-") // x-y
print("senza a capo", terminator: "")
// Interpolazione di stringhe
let eta = 25
let pi = 3.14159
print("Età: \(eta), Pi: \(pi)")
print("Somma: \(2 + 3)") // espressioni dentro \()
🎯 Swift usa l'inferenza di tipo: spesso non devi specificare il tipo, il compilatore lo deduce dal valore. Ma il linguaggio resta fortemente tipizzato.
Variabili e tipi di dato
Swift distingue tra costanti (let, immutabili) e variabili (var, modificabili). Si preferisce let quando il valore non cambia.
let vs var
// Costante: non può essere riassegnata
let nome = "Alice"
// nome = "Bob" // ERRORE!
// Variabile: modificabile
var eta = 25
eta = 26 // OK
// Tipo esplicito (annotazione)
let pi: Double = 3.14159
var contatore: Int = 0
let attivo: Bool = true
let lettera: Character = "A"
Tipo
Descrizione
Esempio
Int
Numeri interi
42, -7
Double / Float
Virgola mobile
3.14
Bool
Booleano
true, false
String
Testo
"ciao"
Character
Singolo carattere
"A"
Type casting (conversione)
let intero = 10
let decimale = 3.5
// Conversione esplicita obbligatoria
let somma = Double(intero) + decimale // 13.5
let troncato = Int(decimale) // 3
// Stringa ↔ numero
let testo = "42"
let numero = Int(testo) // Int? (Optional) → 42
let str = String(intero) // "10"
print(somma, troncato, str)
⚠️ Swift non converte automaticamente tra tipi numerici. Devi convertire esplicitamente: Double(intero). Questo previene errori subdoli.
Operatori
Aritmetici, confronto, logici
let a = 10
let b = 3
// Aritmetici
print(a + b) // 13
print(a - b) // 7
print(a * b) // 30
print(a / b) // 3 (divisione intera)
print(a % b) // 1 (resto)
// Confronto (restituiscono Bool)
print(a > b) // true
print(a == b) // false
print(a != b) // true
// Logici
let x = true, y = false
print(x && y) // false (AND)
print(x || y) // true (OR)
print(!x) // false (NOT)
// Operatori composti
var c = 5
c += 3 // c = 8
c *= 2 // c = 16
Operatori di range
// Range chiuso: include entrambi gli estremi
for i in 1...5 {
print(i) // 1 2 3 4 5
}
// Range semi-aperto: esclude l'estremo destro
for i in 0..<3 {
print(i) // 0 1 2
}
// Operatore ternario
let eta = 20
let stato = eta >= 18 ? "maggiorenne" : "minorenne"
print(stato) // maggiorenne
// Nil-coalescing: valore di default se nil
let nome: String? = nil
print(nome ?? "Anonimo") // Anonimo
Stringhe
Operazioni sulle stringhe
var saluto = "Ciao"
saluto += ", mondo!" // concatenazione
print(saluto) // Ciao, mondo!
let nome = "Swift"
print("Linguaggio: \(nome)") // interpolazione
// Proprietà e metodi
print(saluto.count) // numero caratteri
print(saluto.uppercased()) // CIAO, MONDO!
print(saluto.lowercased())
print(saluto.hasPrefix("Ciao")) // true
print(saluto.hasSuffix("!")) // true
print(saluto.contains("mondo")) // true
print(saluto.isEmpty) // false
// Sostituzione
let nuovo = saluto.replacingOccurrences(
of: "mondo", with: "Swift")
print(nuovo)
Stringhe multilinea e iterazione
// Stringa multilinea con \"\"\"
let poesia = """
Riga uno
Riga due
Riga tre
"""
print(poesia)
// Iterare sui caratteri
for carattere in "Swift" {
print(carattere, terminator: " ") // S w i f t
}
print()
// Dividere una stringa
let csv = "mela,pera,kiwi"
let frutti = csv.split(separator: ",")
print(frutti) // ["mela", "pera", "kiwi"]
Array, Set e Dizionari
Swift offre tre tipi di collezione principali: Array (ordinato), Set (unico, non ordinato) e Dictionary (coppie chiave-valore).
Array
// Array tipizzato
var numeri = [1, 2, 3, 4, 5]
var nomi: [String] = ["Alice", "Bob"]
// Accesso e modifica
print(numeri[0]) // 1
numeri.append(6) // aggiungi in fondo
numeri.insert(0, at: 0) // inserisci all'inizio
numeri.remove(at: 1) // rimuovi per indice
print(numeri.count) // numero elementi
print(numeri.isEmpty) // false
print(numeri.first ?? 0) // primo (Optional)
print(numeri.contains(3)) // true
// Iterazione
for n in numeri {
print(n, terminator: " ")
}
Set e Dictionary
// Set: valori unici, nessun duplicato
var colori: Set<String> = ["rosso", "verde", "blu"]
colori.insert("rosso") // ignorato (già presente)
print(colori.count) // 3
print(colori.contains("verde")) // true
// Operazioni insiemistiche
let a: Set = [1, 2, 3]
let b: Set = [2, 3, 4]
print(a.union(b)) // [1, 2, 3, 4]
print(a.intersection(b)) // [2, 3]
// Dictionary: coppie chiave-valore
var voti = ["Alice": 9, "Luca": 7]
voti["Marco"] = 8 // aggiungi
print(voti["Alice"] ?? 0) // 9 (Optional)
voti.removeValue(forKey: "Luca")
for (nome, voto) in voti {
print("\(nome): \(voto)")
}
Controllo di flusso: if e switch
if / else if / else
let temperatura = 22
if temperatura < 0 {
print("Gelido")
} else if temperatura < 15 {
print("Freddo")
} else if temperatura < 25 {
print("Mite") // stampato
} else {
print("Caldo")
}
// if let: unwrap sicuro di un Optional
let input: String? = "42"
if let valore = input {
print("Valore: \(valore)")
} else {
print("Nessun valore")
}
switch — potente e flessibile
let voto = 8
switch voto {
case 10:
print("Eccellente")
case 8, 9: // più valori
print("Ottimo") // stampato
case 6...7: // range
print("Sufficiente")
case 1..<6:
print("Insufficiente")
default:
print("Voto non valido")
}
// switch con binding e where
let punto = (2, 2)
switch punto {
case let (x, y) where x == y:
print("Sulla diagonale") // stampato
case (let x, 0):
print("Asse X a \(x)")
default:
print("Altrove")
}
💡 A differenza di altri linguaggi, lo switch di Swift non ha bisogno di break (niente fall-through automatico) e deve essere esaustivo: copri tutti i casi o usa default.
Cicli: for e while
for-in
// Su un range
for i in 1...3 {
print("Iterazione \(i)")
}
// Su un array
let frutti = ["mela", "pera", "kiwi"]
for frutto in frutti {
print(frutto)
}
// Con indice (enumerated)
for (i, frutto) in frutti.enumerated() {
print("\(i): \(frutto)")
}
// Su un dizionario
let voti = ["Alice": 9, "Luca": 7]
for (nome, voto) in voti {
print("\(nome) = \(voto)")
}
// Ignorare la variabile con _
for _ in 1...3 {
print("ripeti")
}
while, repeat-while, break/continue
// while: controlla prima di eseguire
var contatore = 3
while contatore > 0 {
print(contatore)
contatore -= 1
}
// repeat-while: esegue almeno una volta
var n = 0
repeat {
print("almeno una volta")
n += 1
} while n < 1
// break e continue
for i in 1...10 {
if i == 3 { continue } // salta il 3
if i == 6 { break } // esci al 6
print(i) // 1 2 4 5
}
Funzioni
Le funzioni in Swift si dichiarano con func. I parametri hanno etichette che rendono le chiamate molto leggibili.
Definizione e chiamata
// Funzione semplice
func saluta(nome: String) -> String {
return "Ciao, \(nome)!"
}
print(saluta(nome: "Alice")) // l'etichetta è obbligatoria
// Più parametri
func somma(_ a: Int, _ b: Int) -> Int {
return a + b
}
print(somma(3, 7)) // _ rende l'etichetta non necessaria
// Etichetta esterna diversa da interna
func muovi(da partenza: String, a destinazione: String) {
print("Da \(partenza) a \(destinazione)")
}
muovi(da: "Roma", a: "Milano")
// Valore di default
func potenza(base: Int, esp: Int = 2) -> Int {
var risultato = 1
for _ in 0..<esp { risultato *= base }
return risultato
}
print(potenza(base: 3)) // 9 (esp default = 2)
print(potenza(base: 2, esp: 4)) // 16
Ritorno multiplo e parametri variadici
// Restituire una tupla
func minMax(_ numeri: [Int]) -> (min: Int, max: Int) {
var minimo = numeri[0], massimo = numeri[0]
for n in numeri {
if n < minimo { minimo = n }
if n > massimo { massimo = n }
}
return (minimo, massimo)
}
let r = minMax([4, 1, 9, 2])
print("min: \(r.min), max: \(r.max)") // min: 1, max: 9
// Parametri variadici (numero variabile)
func media(_ numeri: Double...) -> Double {
let totale = numeri.reduce(0, +)
return totale / Double(numeri.count)
}
print(media(1, 2, 3, 4, 5)) // 3.0
Optional — il cuore della sicurezza di Swift
Un Optional è un tipo che può contenere un valore oppurenil (assenza di valore). Si dichiara con ?. È il modo di Swift per gestire in sicurezza i valori mancanti, eliminando i crash da null pointer.
Dichiarare e srotolare Optional
// Optional: può essere un valore o nil
var nome: String? = "Alice"
nome = nil // permesso
// Unwrap NON sicuro (crash se nil!)
// print(nome!) // pericoloso
// if let: unwrap sicuro
if let n = nome {
print("Nome: \(n)")
} else {
print("Nessun nome")
}
// guard let: esci presto se nil
func saluta(_ nome: String?) {
guard let n = nome else {
print("Nome mancante")
return
}
print("Ciao, \(n)!")
}
saluta("Bob") // Ciao, Bob!
saluta(nil) // Nome mancante
Nil-coalescing e optional chaining
// Nil-coalescing: valore di default
let input: String? = nil
let nome = input ?? "Anonimo"
print(nome) // Anonimo
// Optional chaining: accedi in sicurezza
struct Indirizzo { var citta: String }
struct Persona { var indirizzo: Indirizzo? }
let p = Persona(indirizzo: Indirizzo(citta: "Roma"))
let citta = p.indirizzo?.citta // String? (nil se manca)
print(citta ?? "sconosciuta") // Roma
let p2 = Persona(indirizzo: nil)
print(p2.indirizzo?.citta ?? "sconosciuta") // sconosciuta
🎯 Gli Optional rendono esplicito nel codice quali valori possono mancare. Il compilatore ti obbliga a gestire il caso nil, prevenendo intere categorie di crash.
Closures
Le closures sono blocchi di codice autonomi che puoi passare e usare nel tuo codice, simili alle funzioni anonime (lambda) di altri linguaggi.
Sintassi delle closures
// Closure assegnata a una variabile
let saluta = { (nome: String) -> String in
return "Ciao, \(nome)!"
}
print(saluta("Alice"))
// Closure come argomento (ordinamento)
let numeri = [5, 2, 8, 1, 9]
let ordinati = numeri.sorted(by: { (a, b) in a < b })
print(ordinati) // [1, 2, 5, 8, 9]
// Sintassi abbreviata con $0, $1
let decrescente = numeri.sorted(by: { $0 > $1 })
print(decrescente) // [9, 8, 5, 2, 1]
map, filter, reduce
let numeri = [1, 2, 3, 4, 5, 6]
// map: trasforma ogni elemento
let quadrati = numeri.map { $0 * $0 }
print(quadrati) // [1, 4, 9, 16, 25, 36]
// filter: tiene gli elementi che soddisfano la condizione
let pari = numeri.filter { $0 % 2 == 0 }
print(pari) // [2, 4, 6]
// reduce: combina in un singolo valore
let somma = numeri.reduce(0) { $0 + $1 }
print(somma) // 21
// Concatenazione (functional style)
let risultato = numeri
.filter { $0 % 2 == 0 }
.map { $0 * 10 }
.reduce(0, +)
print(risultato) // 120
Enum
Gli enum definiscono un tipo con un insieme finito di valori possibili. In Swift possono avere valori associati e metodi.
Enum di base e con valori grezzi
// Enum semplice
enum Direzione {
case nord, sud, est, ovest
}
let d = Direzione.nord
switch d {
case .nord: print("Vai a nord")
case .sud: print("Vai a sud")
default: print("Altra direzione")
}
// Enum con valori grezzi (raw values)
enum Pianeta: Int {
case mercurio = 1, venere, terra, marte
}
print(Pianeta.terra.rawValue) // 3
let p = Pianeta(rawValue: 4) // Optional → marte
print(p ?? .terra)
Enum con valori associati
// Ogni caso può trasportare dati diversi
enum Risultato {
case successo(String)
case errore(codice: Int, messaggio: String)
}
func elabora(_ r: Risultato) {
switch r {
case .successo(let msg):
print("OK: \(msg)")
case .errore(let codice, let messaggio):
print("Errore \(codice): \(messaggio)")
}
}
elabora(.successo("Dati salvati"))
elabora(.errore(codice: 404, messaggio: "Non trovato"))
Struct e Classi
Swift offre due modi per creare tipi custom: struct (value type, copiato) e class (reference type, condiviso per riferimento). In Swift si preferiscono spesso le struct.
Struct (value type)
struct Punto {
var x: Double
var y: Double
// Metodo
func distanzaDallOrigine() -> Double {
return (x * x + y * y).squareRoot()
}
}
var p1 = Punto(x: 3, y: 4)
print(p1.distanzaDallOrigine()) // 5.0
// Le struct sono COPIATE all'assegnazione
var p2 = p1
p2.x = 100
print(p1.x) // 3 (p1 non è cambiata!)
print(p2.x) // 100
Class (reference type) ed ereditarietà
class Animale {
var nome: String
init(nome: String) { // inizializzatore
self.nome = nome
}
func verso() -> String {
return "..."
}
}
// Ereditarietà
class Cane: Animale {
override func verso() -> String {
return "Bau!"
}
}
let c = Cane(nome: "Rex")
print("\(c.nome): \(c.verso())") // Rex: Bau!
// Le classi sono CONDIVISE per riferimento
let a1 = Animale(nome: "Gatto")
let a2 = a1
a2.nome = "Micio"
print(a1.nome) // Micio (stesso oggetto!)
Struct
Class
Semantica
Value (copia)
Reference (condivisa)
Ereditarietà
No
Sì
Inizializzatore automatico
Sì (memberwise)
No
Proprietà e metodi
Computed property e property observer
struct Cerchio {
var raggio: Double
// Computed property: calcolata, non memorizzata
var area: Double {
return Double.pi * raggio * raggio
}
var diametro: Double {
get { raggio * 2 }
set { raggio = newValue / 2 }
}
}
var c = Cerchio(raggio: 5)
print(c.area) // 78.53...
c.diametro = 20
print(c.raggio) // 10
Metodi mutating e static
struct Contatore {
var valore = 0
// mutating: può modificare le proprietà di una struct
mutating func incrementa() {
valore += 1
}
// Metodo static (sul tipo, non sull'istanza)
static func crea() -> Contatore {
return Contatore(valore: 0)
}
}
var cont = Contatore.crea()
cont.incrementa()
cont.incrementa()
print(cont.valore) // 2
Protocolli
Un protocollo definisce un insieme di metodi e proprietà che un tipo deve implementare — simile alle interfacce di Java. È alla base della "Protocol-Oriented Programming", lo stile preferito in Swift.
Definire e conformarsi a un protocollo
protocol Descrivibile {
var descrizione: String { get }
func saluta() -> String
}
// Struct che si conforma al protocollo
struct Persona: Descrivibile {
var nome: String
var descrizione: String {
return "Persona di nome \(nome)"
}
func saluta() -> String {
return "Ciao, sono \(nome)"
}
}
let p = Persona(nome: "Alice")
print(p.descrizione) // Persona di nome Alice
print(p.saluta()) // Ciao, sono Alice
// Polimorfismo tramite protocollo
let elementi: [Descrivibile] = [
Persona(nome: "Bob"),
Persona(nome: "Carol")
]
for e in elementi {
print(e.descrizione)
}
💡 Protocolli comuni della libreria standard: Equatable (confronto con ==), Comparable (ordinamento), Codable (serializzazione JSON). Conformarsi a questi sblocca funzionalità potenti.
Gestione degli errori
Swift gestisce gli errori in modo esplicito con throw, try e catch. Le funzioni che possono fallire si marcano con throws.
throw, try, catch
// Definisci gli errori con un enum
enum ErroreBanca: Error {
case saldoInsufficiente(richiesto: Double)
case importoNegativo
}
func preleva(saldo: Double, importo: Double) throws -> Double {
if importo < 0 {
throw ErroreBanca.importoNegativo
}
if importo > saldo {
throw ErroreBanca.saldoInsufficiente(richiesto: importo)
}
return saldo - importo
}
// Gestione con do-catch
do {
let nuovo = try preleva(saldo: 100, importo: 150)
print("Nuovo saldo: \(nuovo)")
} catch ErroreBanca.saldoInsufficiente(let richiesto) {
print("Saldo insufficiente per \(richiesto)€")
} catch {
print("Errore: \(error)")
}
try? e try!
enum ErroreParsing: Error { case nonValido }
func parseNumero(_ s: String) throws -> Int {
guard let n = Int(s) else { throw ErroreParsing.nonValido }
return n
}
// try? → restituisce un Optional (nil se errore)
let a = try? parseNumero("42") // Optional(42)
let b = try? parseNumero("abc") // nil
print(a ?? -1) // 42
print(b ?? -1) // -1
// try! → crash se fallisce (usare solo se sicuri)
let c = try! parseNumero("100")
print(c) // 100
Esercizi pratici
🏋️ Esercizio 1 — FizzBuzz
Stampa i numeri da 1 a 20. Per i multipli di 3 stampa "Fizz", per i multipli di 5 "Buzz", per i multipli di entrambi "FizzBuzz".
Soluzione Esercizio 1
for i in 1...20 {
switch (i % 3, i % 5) {
case (0, 0): print("FizzBuzz")
case (0, _): print("Fizz")
case (_, 0): print("Buzz")
default: print(i)
}
}
🏋️ Esercizio 2 — Gestione Optional
Scrivi una funzione che prenda un array di stringhe, le converta in Int ignorando quelle non valide, e restituisca la somma dei numeri validi.
Crea un protocollo Forma con un metodo area(). Implementalo per Cerchio e Rettangolo, poi stampa l'area totale di un array di forme.
Soluzione Esercizio 3
protocol Forma {
func area() -> Double
}
struct Cerchio: Forma {
var raggio: Double
func area() -> Double { Double.pi * raggio * raggio }
}
struct Rettangolo: Forma {
var base: Double, altezza: Double
func area() -> Double { base * altezza }
}
let forme: [Forma] = [
Cerchio(raggio: 2),
Rettangolo(base: 3, altezza: 4)
]
let totale = forme.reduce(0) { $0 + $1.area() }
print("Area totale: \(totale)") // ~24.56
E ora?
Complimenti! Hai completato il corso sui fondamenti di Swift. Ora padroneggi la sintassi, i tipi, gli Optional, le closures, struct/classi, protocolli e la gestione degli errori — tutto ciò che serve per iniziare a sviluppare app iOS.
Cosa hai imparato:
Variabili (let/var), tipi e type casting esplicito
Stringhe, Array, Set e Dictionary
if, switch esaustivo, cicli for/while
Funzioni con etichette, closures e map/filter/reduce
Optional: il sistema di sicurezza di Swift
Enum con valori associati, struct vs class, protocolli
Gestione degli errori con throw/try/catch
Il passo successivo? Imparare SwiftUI, il framework dichiarativo di Apple per costruire interfacce, e pubblicare la tua prima app sull'App Store con Xcode.