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