Nel linguaggio Swift, il meccanismo di type casting affronta il compito di verificare e convertire un valore da un tipo a un altro durante l'esecuzione. Le sue origini risiedono nella tipizzazione statica e nell'ereditarietà: in Swift è possibile lavorare contemporaneamente con value-types (struct/enum) e reference-types (class/protocol). Il problema si presenta quando un oggetto o un valore è assegnato a una variabile di tipo Any o protocollo — e c'è bisogno di sapere se può essere convertito in un altro (di solito — un tipo più specifico) senza rischiare di far fallire l'applicazione.
Il type casting sicuro consente di sfruttare i vantaggi della sicurezza dei tipi, del polimorfismo dinamico e fornisce protezione dagli errori quando si lavora con collezioni miste o gerarchie di classi.
Swift offre tre forme di type casting:
as — casting sicuro (upcast), quando il compilatore conosce sempre in anticipo il risultatoas? — casting condizionale (optional) (downcast), restituisce un optional, se non è stato possibile convertireas! — casting forzato (forced), provoca un crash a runtime se non è possibile convertireEsempio di codice:
class Animal {} class Dog: Animal { func bark() { print("Woof!") } } let animals: [Animal] = [Dog(), Animal()] for animal in animals { if let dog = animal as? Dog { dog.bark() // safe cast with as? } else { print("Not a dog!") } }
Caratteristiche chiave:
È possibile utilizzare l'operatore as! per il casting tra tipi non correlati (ad esempio, da String a Int)? Cosa succederà?
L'operatore as! provoca sempre un crash a runtime se i tipi non sono compatibili o non c'è ereditarietà o gerarchia comune di protocolli con implementazione. Questo è inaccettabile per la conversione tra tipi fondamentalmente diversi.
Esempio di codice:
let value: Any = "abc" let num = value as! Int // crash: Could not cast value of type 'String' to 'Int'
Cosa succede se si utilizza as? per il casting a un tipo senza alcuna gerarchia?
as? restituisce sempre nil se il casting sicuro non è possibile — anche se i tipi non sono in alcun modo collegati tramite ereditarietà o protocolli implementati.
Esempio di codice:
let value: Any = 5 if let str = value as? String { print(str) } else { print("Can't cast to String") // Questa parte verrà eseguita }
È possibile utilizzare as per il downcasting nelle gerarchie di classi?
No. L'operatore as è adatto solo per l'upcasting (ad esempio, da Dog a Animal, o quando ci si cast a un protocollo implementato). Per il downcasting si applicano sempre as? o as!.
Esempio di codice:
let animal: Animal = Dog() // let dog = animal as Dog // Errore di compile-time let dog = animal as? Dog // Modo corretto
Nel progetto c'era una collezione [Any], in cui per errore venivano inseriti stringhe e numeri. Per elaborare i dati, lo sviluppatore scriveva in diversi punti: let value = item as! String, il che portava a crash quando appariva un numero nell'array.
Vantaggi:
Svantaggi:
Abbiamo riscritto utilizzando enum con tipi associati:
enum Payload { case text(String); case number(Int) } let data: [Payload] = [.text("abc"), .number(1)]
Vantaggi:
Svantaggi: