ProgrammazioneSviluppatore iOS, Middle/Senior

Cosa sono i tipi opachi (some) in Swift, a cosa servono e quando dovrebbero essere applicati al posto dei tipi di protocollo?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Storia della questione: I tipi opachi (some) sono stati introdotti con Swift 5.1 e hanno aperto un nuovo modo di astrarre il valore di ritorno di una funzione o proprietà, quando il tipo è conosciuto dal compilatore, ma nascosto all'utente. Questa è un'alternativa agli esistenziali dei protocolli (any Protocol), ma con un legame rigido a un tipo concreto all'interno della funzione.

Problema: Quando una funzione restituisce un protocollo con associatedtype (ad esempio, Sequence), non è possibile scrivere direttamente:

func makeNumberSequence() -> Sequence { ... } // errore

Gli esistenziali dei protocolli consentono di restituire qualsiasi implementazione, ma non garantiscono lo stesso tipo ad ogni chiamata:

func foo() -> any View { ... }

Questo porta all'imprevedibilità e a una scarsa sicurezza dei tipi.

Soluzione: Utilizzare il tipo di risultato opaco:

func makeNumbers() -> some Sequence { [1, 2, 3] }

Ora il compilatore conosce esattamente il tipo di ritorno effettivo, ma è nascosto all'esterno. Questo permette ottimizzazioni delle prestazioni, sicurezza, consente di utilizzare il DSL di SwiftUI e facilita lo scambio di tipi tra i moduli.

Caratteristiche chiave:

  • Il tipo concreto all'interno del tipo opaco è sempre lo stesso
  • Non supporta associatedtype nel punto di utilizzo, ma solo nella definizione
  • Utilizzato per costruire API dichiarative (ad esempio SwiftUI)

Domande trabocchetto.

Possono i tipi opachi essere utilizzati per memorizzare un valore (come le proprietà di una classe)?

No. I tipi opachi si applicano solo ai valori di ritorno di funzioni o alle proprietà calcolate. Per memorizzare un valore o un array di valori si usano gli esistenziali (any Protocol).

Possono diversi rami di una stessa funzione restituire tipi diversi con some?

No. Il compilatore richiede che entrambi (o tutti) i rami restituiscano lo stesso tipo concreto; altrimenti, ci sarà un errore:

func foo(flag: Bool) -> some Sequence { if flag { return [1, 2, 3] } else { return ["a", "b", "c"] // errore } }

È possibile usare some per identificare il tipo di ritorno tra più funzioni?

No. Ogni funzione con some restituisce il proprio tipo nascosto unico, anche se in effetti si tratta dello stesso array. Non è possibile utilizzare il risultato di una funzione come parametro in un'altra, se entrambe utilizzano some con protocolli diversi o tipi nascosti diversi.

Errori comuni e anti-pattern

  • Cercano di restituire tipi diversi tramite some (errore di compilazione)
  • Usano some dove sono richiesti gli esistenziali dei protocolli (ad esempio, per memorizzare stati)
  • Perdono ottimizzazioni continuando a utilizzare i vecchi tipi di protocollo

Esempi dalla vita reale

Caso negativo

Nel progetto tutto viene restituito tramite any Protocol, le collezioni perdono la tipizzazione, si verificano bug durante il downcast, rallenta l'ottimizzazione dei tempi di compilazione.

Pro:

  • Architettura flessibile con la possibilità di combinare tipi diversi

Contro:

  • Perdita di sicurezza dei tipi, riduzione dell'efficienza, problemi di cast

Caso positivo

Nella progettazione SwiftUI, i componenti restituiscono some View, ogni modulo definisce chiaramente il tipo interno. La dimensione del bundle si riduce, la compilazione si accelera.

Pro:

  • Miglioramento delle prestazioni, aumento della sicurezza dei tipi

Contro:

  • Alcuna perdita di flessibilità quando è necessaria la memorizzazione dinamica