W języku Swift mechanizm rzutowania typów rozwiązując problem sprawdzenia i przekształcenia wartości z jednego typu na inny w czasie wykonywania. Jego korzenie sięgają statycznej typizacji oraz dziedziczenia: w Swift można jednocześnie pracować z typami wartości (struct/enum) oraz typami referencyjnymi (class/protocol). Problem pojawia się, gdy obiekt lub wartość trafia do zmiennej typu Any lub protokołu — i trzeba ustalić, czy można go przekształcić na inny (częściej — bardziej specyficzny) typ bez ryzyka awarii aplikacji.
Bezpieczne rzutowanie typów pozwala wykorzystać zalety typowej bezpieczeństwa, dynamicznego polimorfizmu oraz zapewnia ochronę przed błędami podczas pracy z mieszanymi kolekcjami lub hierarchiami klas.
Swift oferuje trzy formy rzutowania typów:
as — bezpieczne rzutowanie (upcast), gdy kompilator zna wynik zawsze z wyprzedzeniemas? — warunkowe (optional) rzutowanie (downcast), zwraca optional, jeśli rzutowanie nie mogło się powieśćas! — wymuszone (forced) rzutowanie, powoduje awarię wykonania (runtime crash), jeśli rzutowanie nie jest możliwePrzykład kodu:
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() // bezpieczne rzutowanie z as? } else { print("Nie jest psem!") } }
Kluczowe cechy:
Czy można używać operatora as! do rzutowania między niespokrewnionymi typami (na przykład, z String na Int)? Co się z tym stanie?
Operator as! zawsze prowadzi do awarii wykonania, jeśli typy są niekompatybilne lub nie ma między nimi dziedziczenia lub wspólnej hierarchii protokołów z implementacją. Jest to niedopuszczalne dla rzutowania między fundamentalnie różnymi typami.
Przykład kodu:
let value: Any = "abc" let num = value as! Int // awaria: Nie można rzutować wartości typu 'String' na 'Int'
Co się stanie, jeśli użyjesz as? podczas rzutowania do typu, z którym nie ma żadnej hierarchii?
as? zawsze zwraca nil, jeśli bezpieczne rzutowanie jest niemożliwe — nawet jeśli typy w żaden sposób nie są ze sobą związane dziedziczeniem lub implementowanymi protokołami.
Przykład kodu:
let value: Any = 5 if let str = value as? String { print(str) } else { print("Nie można rzutować na String") // Ta gałąź zostanie wykonana }
Czy można używać as do rzutowań w dół w hierarchii klas (downcasting)?
Nie. Operator as nadaje się tylko do upcast (na przykład, z Dog na Animal, lub przy rzutowaniu na zrealizowany protokół). Do rzutowań w dół zawsze stosuje się as? lub as!.
Przykład kodu:
let animal: Animal = Dog() // let dog = animal as Dog // Błąd kompilacji let dog = animal as? Dog // Prawidłowy sposób
W projekcie była kolekcja [Any], do której przez pomyłkę dodawano ciągi i liczby. W celu przetwarzania danych programista w kilku miejscach pisał: let value = item as! String, co prowadziło do awarii przy pojawieniu się liczby w tablicy.
Zalety:
Wady:
Przepisaliśmy z zastosowaniem powiązanych typów enum:
enum Payload { case text(String); case number(Int) } let data: [Payload] = [.text("abc"), .number(1)]
Zalety:
Wady: