Swift Type Casting
Type casting checks or converts the type of an instance at runtime. Swift gives you three tools: is to check a type, as? to safely attempt a conversion, and as! to force one. Think of it like checking what is inside a sealed box — you first verify the type, then decide how to handle its contents.
Why Type Casting Is Needed
When different types share a common parent class or protocol, you sometimes store them together in one collection. Type casting lets you discover and use the specific type of each item at runtime.
class Animal {
var name: String
init(name: String) { self.name = name }
}
class Dog: Animal {
func fetch() { print("\(name) fetches!") }
}
class Cat: Animal {
func purr() { print("\(name) purrs...") }
}
let animals: [Animal] = [Dog(name: "Rex"), Cat(name: "Whiskers"), Dog(name: "Buddy")]Checking Type With is
The is operator returns true if an instance belongs to a given type or any of its subclasses.
for animal in animals {
if animal is Dog {
print("\(animal.name) is a dog.")
} else if animal is Cat {
print("\(animal.name) is a cat.")
}
}
// Output:
// Rex is a dog.
// Whiskers is a cat.
// Buddy is a dog.Diagram: Type Hierarchy and Casting
Animal (superclass)
/ \
Dog Cat
fetch() purr()
animals array: [Dog, Cat, Dog] — stored as [Animal]
animal is Dog → true/false
animal as? Dog → Dog? (optional)
animal as! Dog → Dog (crash if wrong)
Downcasting With as?
A downcast converts a parent type to a more specific child type. Use as? for a safe optional result — it returns nil if the cast fails instead of crashing.
for animal in animals {
if let dog = animal as? Dog {
dog.fetch()
} else if let cat = animal as? Cat {
cat.purr()
}
}
// Output:
// Rex fetches!
// Whiskers purrs...
// Buddy fetches!Force Downcasting With as!
Use as! only when you are 100% certain the cast will succeed. A failed force cast crashes your app immediately.
let first = animals[0] as! Dog // Safe — we know index 0 is a Dog
first.fetch() // Output: Rex fetches!Upcasting With as
Upcasting converts a child type to its parent. This always succeeds, so no ? or ! is needed.
let myDog = Dog(name: "Max")
let asAnimal = myDog as Animal // Always safe
print(asAnimal.name) // Output: Max
// asAnimal.fetch() // ← Error: Animal has no fetch()Type Casting With Any and AnyObject
Any — Holds Any Type
Any can hold a value of absolutely any type, including structs, enums, and functions.
var mixed: [Any] = [42, "hello", true, 3.14, Dog(name: "Spot")]
for item in mixed {
switch item {
case let number as Int:
print("Int: \(number)")
case let text as String:
print("String: \(text)")
case let flag as Bool:
print("Bool: \(flag)")
case let decimal as Double:
print("Double: \(decimal)")
case let dog as Dog:
print("Dog: \(dog.name)")
default:
print("Unknown type")
}
}
// Output:
// Int: 42
// String: hello
// Bool: true
// Double: 3.14
// Dog: SpotAnyObject — Holds Any Class Instance
AnyObject holds instances of any class type only (not structs or enums).
var objects: [AnyObject] = [Dog(name: "Rex"), Cat(name: "Luna")]
for obj in objects {
if let d = obj as? Dog { print("Dog: \(d.name)") }
if let c = obj as? Cat { print("Cat: \(c.name)") }
}Type Casting With Protocols
The same casting tools work when checking protocol conformance.
protocol Flyable {
func fly()
}
class Bird: Animal, Flyable {
func fly() { print("\(name) is flying!") }
}
let zoo: [Animal] = [Dog(name: "Rex"), Bird(name: "Tweety"), Cat(name: "Mimi")]
for creature in zoo {
if let flyer = creature as? Flyable {
flyer.fly()
} else {
print("\(creature.name) cannot fly.")
}
}
// Output:
// Rex cannot fly.
// Tweety is flying!
// Mimi cannot fly.type(of:) — Check Type at Runtime
Use type(of:) to get the actual runtime type of any value.
let value: Any = "Hello"
print(type(of: value)) // Output: String
let num: Any = 42
print(type(of: num)) // Output: Int
for animal in animals {
print(type(of: animal))
}
// Output: Dog Cat DogDiagram: Casting Direction
Upcast (always safe, use as): Dog → Animal guaranteed success Downcast (may fail, use as?): Animal → Dog returns Dog? (nil if it's actually a Cat) Force downcast (risky, use as!): Animal → Dog returns Dog (CRASH if it's a Cat) Type check (use is): animal is Dog returns true or false
Summary
Type casting lets you check (is), safely convert (as?), or force-convert (as!) between related types at runtime. Use Any to store mixed-type collections and switch with as patterns to handle each type. Always prefer as? over as! to avoid crashes. Type casting connects Swift's static type system to the dynamic needs of real apps.
