In Swift, the type casting mechanism addresses the task of checking and converting a value from one type to another at runtime. Its roots lie in static typing and inheritance: in Swift, you can work simultaneously with value types (struct/enum) and reference types (class/protocol). Problems arise when an object or value enters a variable of type Any or a protocol — and it is necessary to know whether it can be converted to another (often more specific) type without the risk of crashing the application.
Safe type casting allows you to leverage the advantages of type safety, dynamic polymorphism, and protects against errors when working with mixed collections or class hierarchies.
Swift provides three forms of type casting:
as — safe casting (upcast), when the compiler always knows the result in advanceas? — conditional (optional) casting (downcast), returns optional if casting failsas! — forced casting, causes a runtime crash if casting is not possibleCode example:
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!") } }
Key features:
Can the as! operator be used for casting between unrelated types (for example, from String to Int)? What will happen?
The as! operator always results in a runtime crash if the types are incompatible or if there is no inheritance or common protocol hierarchy with implementation. This is not allowed for conversion between fundamentally different types.
Code example:
let value: Any = "abc" let num = value as! Int // crash: Could not cast value of type 'String' to 'Int'
What happens if you use as? when casting to a type with which there is no hierarchy?
as? always returns nil when safe casting is not possible — even if the types are not related by inheritance or implemented protocols.
Code example:
let value: Any = 5 if let str = value as? String { print(str) } else { print("Can't cast to String") // This branch will execute }
Is it possible to use as for downcasting in class hierarchies?
No. The as operator is only suitable for upcasting (for example, from Dog to Animal, or when casting to an implemented protocol). For downcasting, as? or as! is always used.
Code example:
let animal: Animal = Dog() // let dog = animal as Dog // Compile-time error let dog = animal as? Dog // Correct way
In a project, there was a collection [Any], into which strings and numbers were mistakenly added. To process the data, the developer wrote in several places: let value = item as! String, which led to a crash when a number appeared in the array.
Pros:
Cons:
Refactored using associated types in enums:
enum Payload { case text(String); case number(Int) } let data: [Payload] = [.text("abc"), .number(1)]
Pros:
Cons: