Swift
class introspection
generics
programming
iOS development

Swift class introspection generics

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Swift, reflection and generics intersect in a limited but useful way. You can inspect runtime values with tools such as type(of:) and Mirror, and you can write generic code that preserves type information at compile time. What you cannot do is expect full dynamic runtime introspection of generic types in the same style as a highly dynamic language.

Start with the Two Different Mechanisms

Swift gives you two different capabilities that are often mixed together:

  • generic type information known at compile time
  • runtime reflection of values and types

Generics are primarily a compile-time feature. Reflection is a runtime feature. They overlap, but they are not the same system.

Inspecting the Dynamic Type of a Value

The simplest runtime introspection tool is type(of:).

swift
1class Animal {}
2class Dog: Animal {}
3
4let value: Animal = Dog()
5print(type(of: value))

This prints the dynamic runtime type of the instance, which is often the first thing people want from introspection.

You can also compare metatypes directly:

swift
if type(of: value) == Dog.self {
    print("value is a Dog")
}

That works well when you need type-based branching.

Generic Functions Preserve Type Information

Inside a generic function, Swift keeps the concrete type parameter available to the compiler and to some runtime metadata operations.

swift
1func describeType<T>(_ value: T) {
2    print("static generic type:", T.self)
3    print("dynamic runtime type:", type(of: value))
4}
5
6describeType(42)
7describeType("hello")

For value types, T.self and type(of: value) are often the same. For protocol and class hierarchies, they can differ in meaningful ways depending on how the function is called.

Using Mirror for Structural Reflection

Mirror is Swift's main public reflection API for inspecting stored properties and child values.

swift
1struct User {
2    let id: Int
3    let name: String
4}
5
6let user = User(id: 1, name: "Alice")
7let mirror = Mirror(reflecting: user)
8
9for child in mirror.children {
10    print(child.label ?? "_", child.value)
11}

Mirror is useful for debugging, generic display code, and lightweight inspection. It is not intended to replace a full metadata or serialization framework.

Introspecting Generic Types

If you have a generic class, you can still inspect its concrete instantiation through normal Swift type operations.

swift
1class Box<T> {
2    let value: T
3    init(_ value: T) { self.value = value }
4}
5
6let intBox = Box(123)
7print(type(of: intBox))
8print(Box<Int>.self)

This lets you distinguish Box<Int> from Box<String> at the type level.

What Swift does not provide as a clean public API is a rich structured reflection interface for "give me the generic parameter list of an arbitrary runtime object and let me manipulate it dynamically". You can often observe the type name, but generic programming in Swift is not designed around deep runtime metaobject manipulation.

Protocol Constraints Are Often Better Than Reflection

A lot of developers reach for introspection when a protocol constraint or overload would be cleaner.

swift
1protocol Named {
2    var name: String { get }
3}
4
5func printName<T: Named>(_ value: T) {
6    print(value.name)
7}

This is usually preferable to reflecting over a value and hoping it has a field called name.

In Swift, strong type constraints are normally the first tool, and reflection is the fallback for diagnostics or generic infrastructure.

Objective-C Runtime Is a Different Layer

If a type is NSObject-based and exposed to the Objective-C runtime, additional introspection tools such as NSStringFromClass and Objective-C runtime functions become available.

swift
1import Foundation
2
3class MyViewController: NSObject {}
4print(NSStringFromClass(MyViewController.self))

That can help in UIKit and Foundation-heavy code, but it is not the general answer for all Swift generic introspection problems.

Common Pitfalls

A common mistake is expecting Mirror to be a full runtime metadata system for arbitrary generic manipulation. It is much lighter-weight than that.

Another mistake is using reflection where protocol constraints or normal generic design would be clearer and safer.

People also often confuse T.self with type(of: value). One refers to the generic type parameter in context, and the other refers to the runtime dynamic type of the value.

Finally, do not assume Objective-C runtime tools apply to pure Swift value types or non-Objective-C class hierarchies in the same way.

Summary

  • Swift generics are mainly compile-time, while reflection is mainly runtime
  • Use type(of:) to inspect the dynamic type of a value at runtime
  • Use T.self inside generic code to refer to the concrete generic type parameter
  • Use Mirror for lightweight structural reflection of stored properties
  • Reflection is limited compared with dynamic languages, so prefer protocols and generic constraints when possible
  • Objective-C runtime introspection is useful for bridged classes, but it is not the general solution for all Swift generic type questions

Course illustration
Course illustration

All Rights Reserved.