ProgrammazioneSenior iOS Developer

Cosa sono i 'opaque types' (some) in Swift, quando e perché usarli, e come si differenziano dai protocol con associated type?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Storia della questione:

In Swift fino alla versione 5, quando si restituiva un valore conforme a un protocollo con associated type, ci si trovava spesso ad affrontare delle limitazioni: il tipo non poteva essere utilizzato come tipo di ritorno direttamente, era richiesto il type erasure. Per migliorare la leggibilità e le prestazioni, sono stati introdotti gli opaque types: valori restituiti tramite la parola chiave some, che consentono di descrivere astrazioni nelle interfacce pubbliche.

Problema:

Quando è necessario nascondere il tipo reale del valore restituito, mantenendo l'astrazione tramite un protocollo, ma evitando i costi del dynamic dispatch e del type erasure. Ad esempio, restituendo collezioni, sequenze, componenti view.

Soluzione:

Gli opaque types consentono di restituire un tipo conforme al protocollo, nascondendo la sua implementazione specifica. Il compilatore conosce il tipo reale, ma la parte chiamante no.

Esempio:

protocol Shape { func area() -> Double } struct Circle: Shape { var radius: Double func area() -> Double { Double.pi * radius * radius } } func makeCircle() -> some Shape { return Circle(radius: 3) } let s = makeCircle() print(s.area()) // funziona

Caratteristiche chiave:

  • L'opaque type è sempre lo stesso tipo, nascosto dietro un protocollo, dichiarato nel return tramite some
  • Più veloce e rigoroso rispetto al type erasure, consente di lavorare con associated type nei protocolli
  • Non consente di restituire tipi diversi in rami diversi (il tipo deve essere lo stesso per tutti i return)

Domande trabocchetto.

Qual è la differenza tra l'opaque type (some Protocol) e il tipo restituito da un Protocol?

L'opaque type ha un'implementazione concreta durante la compilazione (anche se è nascosta dall'esterno). Quando si restituisce Protocol, viene eseguito il dynamic dispatch, non c'è type safety se ci sono associated type.

Posso restituire tipi diversi utilizzando some Protocol in una sola funzione?

No. Tutti i return devono restituire lo stesso tipo reale:

func maker(flag: Bool) -> some Shape { if flag { return Circle(radius: 3) } else { return Square(size: 2) // Errore: il tipo di ritorno non coincide } }

Può l'associatedtype all'interno di un Protocol essere utilizzato tramite some Protocol?

Sì. È proprio per questo (in primo luogo) che è necessario l'opaque type:

protocol View { associatedtype Body } func makeView() -> some View { /* ... */ }

Errori tipici e anti-pattern

  • Confusione tra some Protocol e Protocol — casi e limitazioni diversi
  • Violazione della regola di omogeneità del tipo restituito in tutti i rami
  • Applicazione di some dove è più semplice protocol o typealias

Esempio dalla vita reale

Caso negativo

La funzione restituisce un protocol senza opaque, non consente di utilizzare metodi con associatedtype, è necessario un complesso type erasure, il codice non si compila o funziona in modo subottimale.

Vantaggi:

  • Flessibilità nelle astrazioni tipo AnySequence

Svantaggi:

  • Perdite di type safety, basse prestazioni
  • Non lavorano con associated types

Caso positivo

ViewBuilder in SwiftUI utilizza some View, nascondendo i dettagli, migliorando la sicurezza dei tipi, accelerando la compilazione e il runtime.

Vantaggi:

  • API leggibile, type safety, niente dynamic dispatch
  • Facilità di supporto

Svantaggi:

  • Non è possibile restituire tipi diversi in una sola funzione