Il pattern matching è una caratteristica fondamentale di Swift che rende il codice più sicuro e pulito. Storicamente, il pattern matching si è evoluto come un mezzo per lavorare in modo più espressivo e sicuro con gli enum, in particolare con valori associati. A differenza di altri linguaggi, Swift permette di estrarre valori annidati direttamente nelle espressioni if, guard e switch.
Problema: per enum non standard, specialmente con valori associati annidati, è facile commettere errori nella struttura dei case o sottovalutare le limitazioni del main switch. Inoltre, spesso non è chiara la differenza tra l'uso di if case, guard case e il classico switch.
Soluzione: applicare diversi approcci al pattern matching a seconda dello scenario e, in strutture complesse, specificare chiaramente i nomi e utilizzare where.
Esempio di codice:
enum NetworkState { case success(User) case failure(Error, code: Int) case loading(progress: Double) } let state = NetworkState.failure(SomeError(), code: 401) if case let .failure(error, code) = state, code == 401 { print("Non autorizzato: \(error)") } guard case .success(let user) = state else { return } print(user) switch state { case .success(let user): print("Benvenuto, \(user.name)") case .failure(let error, let code) where code == 404: print("Non trovato: \(error)") case .failure(_, let code): print("Errore con codice: \(code)") case .loading(let progress): print("Progresso: \(progress)") }
Caratteristiche chiave:
Cosa succede se non si specificano tutti i case dell'enum nello switch?
Errore di compilazione (se l'enum è non-optional) o avviso/richiesta di default case, se non tutte le coperture sono considerate. Per Optionals è sufficiente indicare solo .some e .none.
È possibile fare pattern matching con enum annidati e valori associati?
Sì. Ma la sintassi si complica. Per i valori annidati, è possibile estrarre immediatamente diversi livelli:
enum Outer { case inner(Inner) } enum Inner { case item(id: Int) } let e = Outer.inner(.item(id: 5)) if case let .inner(.item(id)) = e { print(id) }
Qual è la differenza tra guard case e if case nel pattern matching?
guard case verifica la condizione ed esegue un exit-block (return, throw, break) in caso di fallimento. Viene solitamente utilizzato per requisiti: se non corrisponde, uscire.
if case è una costruzione adatta per gestori condizionali su una riga senza uscire dalla funzione.
Un sviluppatore utilizza switch per un enum con diversi case, ma non copre un nuovo case. Il codice compila, ma il nuovo scenario non viene gestito e l'applicazione va in crash a runtime.
Pro:
Contro:
L'sviluppatore copre esplicitamente tutti i case, utilizza where per filtri locali, estrae valori annidati tramite let annidato.
Pro:
Contro: