Type Casting and Reflection
|
This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline. This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Type casting checks a value’s type, or treats it as an instance of a different type in the same class hierarchy or protocol conformance; Swift also lets code inspect a value’s structure at runtime through reflection, most often for debugging or generic logging rather than everyday control flow.
is, as?, as!, and Upcasting with as
class MediaItem {
var name: String
init(name: String) { self.name = name }
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
let library: [MediaItem] = [Movie(name: "Casablanca", director: "Curtiz"), Song(name: "Blue", artist: "Mitchell")]
for item in library {
if item is Movie { // `is`: a yes/no type check, no value produced
print("\(item.name) is a movie")
}
if let movie = item as? Movie { // `as?`: conditional downcast, nil on failure
print("directed by \(movie.director)")
}
}
let firstMovie = library[0] as! Movie // `as!`: forced downcast -- traps if it fails
let upcast: MediaItem = firstMovie as MediaItem // `as`: upcasting is always safe and always succeeds
is answers whether a value’s dynamic type is, or inherits from, a given type — useful on its own only as a
yes/no check, since it produces no usable value of the narrower type. as? performs the same check and, on
success, returns the value retyped as the target type wrapped in an Optional, nil otherwise — the safe,
everyday way to downcast. as! forces that same downcast and unwraps it immediately, crashing at runtime if the
value is not actually of the target type — reach for it only where the type is already known to be correct by
some invariant the type system itself cannot express. Plain as (no ? or !) upcasts to a known superclass or
supertype, which can never fail and is really just a type annotation the compiler already knows to be true.
Any vs. AnyObject
var things: [Any] = [] // Any: absolutely anything -- a class, struct, enum, function...
things.append(42)
things.append("hello")
things.append(Movie(name: "Casablanca", director: "Curtiz"))
things.append({ (x: Int) -> Int in x * 2 })
var objects: [AnyObject] = [] // AnyObject: any *class* instance specifically
objects.append(Movie(name: "Alien", director: "Scott"))
Any can represent an instance of any type at all — a class, a struct, an enum, a function type, even another
existential — with no requirement of conformance to anything; AnyObject is narrower, representing any instance
of any class type only, which matters when code specifically needs reference semantics (e.g. an
NSObject-rooted API, or a dictionary keyed by object identity) rather than "any value whatsoever" (see
Opaque and Boxed Protocol Types for any
P, which erases to a specific protocol’s requirements rather than to "any type" or "any class").
Casting in switch
for item in library {
switch item {
case let movie as Movie: // pattern-matches AND downcasts in one step
print("Movie: \(movie.name), directed by \(movie.director)")
case let song as Song:
print("Song: \(song.name), by \(song.artist)")
default:
print("Other: \(item.name)")
}
}
A switch case can combine a type check with a downcast using as directly in the case pattern: case let movie
as Movie matches only when item’s dynamic type is (or inherits from) `Movie, and binds movie as that
narrower type for the case’s body — the same mechanism covered generally in
Pattern Matching, specialized here to type identity.
Metatypes: T.Type, .self, type(of:), and Self
let movieType: Movie.Type = Movie.self // Movie.Type: the *type* of Movie itself, a metatype
let dynamicType = type(of: firstMovie) // type(of:): the *dynamic* (runtime) type of a value
print(movieType == dynamicType) // metatypes are themselves comparable
class Shape3 {
required init() {}
static func make() -> Self { // `Self` in a class: the *actual* subclass at the call site
Self() // constructs whichever concrete type Self resolves to
}
}
class Square3: Shape3 {}
let made = Square3.make() // Self resolves to Square3 here, not Shape3
T.Type is the type of a type — Movie.Type is the metatype naming Movie (and any of its subclasses)
themselves, distinct from Movie naming an instance; T.self is the metatype value for T, the thing that
actually has type T.Type. type(of:) returns a value’s dynamic metatype (the actual runtime type, which for
a class instance may be a subclass of the value’s declared static type), whereas T.self is always the static
type written at that point in the source. Self (capitalized), used inside a class, protocol, or extension,
refers to whichever concrete type is actually running at the call site — inside a static func on a base
class, Self resolves to the subclass the method was actually called on, which is what lets `Shape3.make()’s
pattern produce the right concrete type from a single implementation shared by every subclass.
Bridging Casts to Foundation Types
import Foundation
let nsNumber = NSNumber(value: 42)
let backToInt = nsNumber as? Int // bridging cast: NSNumber -> Int, "for free"
let nsString: NSString = "hello" as NSString // String -> NSString, also bridged
let backToString = nsString as String
Several Foundation classes bridge to Swift standard-library types with an ordinary as/as? cast rather than an
explicit conversion initializer: String bridges to and from NSString, and numeric types bridge to and from
NSNumber, because the two languages' runtimes cooperate to make the same underlying storage usable as either
type. This bridging is available on both Apple platforms and (via swift-corelibs-foundation) on Linux, and is
covered further, alongside `Codable’s own Foundation interplay, in
Foundation Essentials and
Interoperability with C,
Objective-C, and C++.
Reflection with Mirror
struct Point3D {
var x: Double
var y: Double
var z: Double
}
let mirror = Mirror(reflecting: Point3D(x: 1, y: 2, z: 3))
print(mirror.displayStyle as Any) // .struct, .class, .enum, .tuple, .optional, .collection...
for child in mirror.children { // each stored property, as a (label, value) pair
print("\(child.label ?? "?") = \(child.value)")
}
protocol JSONSerializable {}
extension JSONSerializable {
func asDictionary() -> [String: Any] { // a serializer built entirely on reflection, no Codable needed
var result: [String: Any] = [:]
for child in Mirror(reflecting: self).children {
if let label = child.label {
result[label] = child.value
}
}
return result
}
}
struct CustomMirrored: CustomReflectable {
var secret = "hidden"
var customMirror: Mirror { // overrides what Mirror(reflecting:) reports for this type
Mirror(self, children: ["secret": "***"])
}
}
Mirror(reflecting:) produces a runtime description of a value’s structure without requiring the type to
opt into anything: displayStyle reports the general shape of the value (struct, class, enum, tuple, optional,
collection, or nil when none applies), and children walks every stored property as a (label, value) pair — exactly what a hand-rolled serializer (like asDictionary() above) can build on to turn arbitrary structs into a
dictionary with no per-type code at all. A type that wants to control what its own mirror reports — to redact a
field, or reshape how a custom container reflects — conforms to CustomReflectable and supplies its own
customMirror, as CustomMirrored does above.
dump and CustomStringConvertible/CustomDebugStringConvertible
dump(Point3D(x: 1, y: 2, z: 3))
// prints a full, indented, reflection-driven tree of the value and all its stored properties
struct Money: CustomStringConvertible, CustomDebugStringConvertible {
let cents: Int
var description: String { "$\(Double(cents) / 100)" } // used by print(...) and string interpolation
var debugDescription: String { "Money(cents: \(cents))" } // used by debugPrint(...) and po in the debugger
}
print(Money(cents: 250)) // "$2.5" -- description
debugPrint(Money(cents: 250)) // "Money(cents: 250)" -- debugDescription
dump(_:) prints a value’s entire structure recursively, built directly on the same Mirror machinery above — useful for quickly inspecting a deeply nested value without writing any description logic. description
(CustomStringConvertible) and debugDescription (CustomDebugStringConvertible) instead let a type supply its
own human-readable and debug-oriented textual representations, used automatically by print, string
interpolation, and the debugger respectively, and are the conventional alternative to reflection whenever a
type’s textual form should be curated rather than derived structurally.
See Also
-
Protocols —
is/as?for checking and casting to protocol conformance, alongside class-hierarchy casts. -
Opaque and Boxed Protocol Types —
any Pas a boxed existential, and implicitly opened existentials. -
Inheritance — the class hierarchies
is/as?/as!cast across. -
Pattern Matching —
switchpatterns in full, including theascasting pattern. -
Codable and Serialization — the compiler- synthesized alternative to a
Mirror-based serializer. -
Foundation Essentials — Foundation’s bridged types in full.