Рефлексия и интроспекция в Swift: заглядываем в душу кода

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Рефлексия и интроспекция в Swift: заглядываем в душу кода

Вы когда-нибудь задумывались, как Swift «видит» собственные структуры и классы? Как можно на лету узнать тип объекта, его свойства или даже подпись метода, не зная ничего о нём заранее? Добро пожаловать в мир рефлексии и интроспекции — двух мощных механизмов, которые превращают статически типизированный Swift в гибкий инструмент для метапрограммирования.

Сегодня мы не просто разберём теорию. Мы напишем код, который сам себя исследует, научимся динамически проверять протоколы и даже сериализовать объекты без Codable. Поехали!

Что такое рефлексия и интроспекция? Не путайте эти понятия

Начнём с чёткой терминологии, чтобы не запутаться в дебрях Swift runtime.

  • Интроспекция — это способность программы «посмотреть на себя» во время выполнения: узнать тип объекта, количество свойств, имена методов. В Swift это реализовано через протокол Mirror и операторы is, as?, type(of:).
  • Рефлексия — более широкое понятие. Это не только наблюдение, но и возможность влиять на структуру программы на лету: вызывать методы по имени, менять свойства, создавать новые типы. В Swift рефлексия ограничена (в отличие от Python или Ruby), но её возможности впечатляют.

Главное отличие: интроспекция — это чтение, рефлексия — чтение + запись. Swift позиционируется как безопасный язык, поэтому полная рефлексия (как в Objective-C) здесь недоступна для чистых Swift-типов. Но для классов, унаследованных от NSObject, открывается больше возможностей.

Инструменты интроспекции в Swift: от простого к сложному

1. Операторы is и as? — классика жанра

Самый простой способ узнать тип объекта — использовать проверку через is:

let value: Any = "Hello, Swift!"

if value is String {
    print("Это строка!")
}

if let number = value as? Int {
    print("Это число: \(number)")
} else {
    print("Это не число")
}

Здесь мы используем is для проверки типа и as? для безопасного приведения. Это основа любой интроспекции в Swift — вы постоянно спрашиваете объект: «Ты кто?».

2. type(of:) — узнаём тип динамически

Функция type(of:) возвращает метатип (Any.Type) переданного значения. Это полезно, когда нужно получить точный динамический тип, а не объявленный статический:

class Animal { }
class Dog: Animal { }

let pet: Animal = Dog()
print(type(of: pet)) // Dog

// Сравнение типов
if type(of: pet) == Dog.self {
    print("Это точно собака!")
}

Обратите внимание: type(of:) возвращает динамический тип, а не статический (Animal). Это ключевое отличие от простого Self.

3. Mirror — рентгеновское зрение для объектов

А теперь самое интересное. Mirror — это структура, которая позволяет заглянуть внутрь любого объекта и получить список его свойств, их имена и значения. Настоящий инструмент для рефлексии в Swift.

Базовый пример:

struct User {
    let name: String
    var age: Int
    private var secretKey: String
}

let user = User(name: "Алексей", age: 30, secretKey: "abc123")
let mirror = Mirror(reflecting: user)

print("Тип объекта: \(mirror.subjectType)")
print("Количество свойств: \(mirror.children.count)")

for child in mirror.children {
    if let label = child.label {
        print("\(label): \(child.value)")
    }
}

Вывод:

Тип объекта: User
Количество свойств: 3
name: Алексей
age: 30
secretKey: abc123

Обратите внимание: Mirror видит даже приватные свойства! Это мощно, но и опасно — не стоит полагаться на это в продакшне для критичных данных.

Как работает Mirror? Разбираем под капотом

Mirror использует Swift runtime для обхода всех stored properties объекта. Он не вызывает геттеры computed properties — только те свойства, которые реально хранятся в памяти. Также Mirror поддерживает различные стили отображения (DisplayStyle): структуры, классы, перечисления, кортежи и т.д.

Полезный лайфхак: можно рекурсивно обойти весь граф объектов:

func dumpAllProperties(_ value: Any, indent: Int = 0) {
    let mirror = Mirror(reflecting: value)
    let prefix = String(repeating: "  ", count: indent)
    
    for child in mirror.children {
        if let label = child.label {
            print("\(prefix)\(label): \(child.value)")
            let childMirror = Mirror(reflecting: child.value)
            if childMirror.children.count > 0 && childMirror.displayStyle != .optional {
                dumpAllProperties(child.value, indent: indent + 1)
            }
        }
    }
}

dumpAllProperties(user)

Этот код пройдёт по всем вложенным структурам и распечатает их свойства. Идеально для отладки сложных моделей данных.

Проверка протоколов динамически: is для протоколов

В Swift можно проверять не только типы, но и соответствие протоколам во время выполнения. Это особенно полезно при работе с гетерогенными коллекциями:

protocol Flyable {
    func fly()
}

protocol Swimmable {
    func swim()
}

struct Bird: Flyable {
    func fly() { print("Птица летит") }
}

struct Fish: Swimmable {
    func swim() { print("Рыба плывёт") }
}

struct Duck: Flyable, Swimmable {
    func fly() { print("Утка летит") }
    func swim() { print("Утка плывёт") }
}

let creatures: [Any] = [Bird(), Fish(), Duck()]

for creature in creatures {
    if let flyable = creature as? Flyable {
        flyable.fly()
    }
    if let swimmable = creature as? Swimmable {
        swimmable.swim()
    }
}

Динамическая проверка протоколов — мощный паттерн, который часто используется в архитектурах типа protocol-oriented programming и при работе с dependency injection.

Рефлексия для классов NSObject: наследие Objective-C

Если ваш класс наследуется от NSObject, вы получаете доступ к полной Objective-C runtime рефлексии. Это открывает возможности, недоступные для чистых Swift-типов:

import Foundation

class ReflectionDemo: NSObject {
    @objc var name: String = "Swift"
    @objc dynamic var value: Int = 42
    
    @objc func sayHello() {
        print("Hello from \(name)!")
    }
}

let demo = ReflectionDemo()

// Получаем список всех свойств
var propertyCount: UInt32 = 0
let properties = class_copyPropertyList(ReflectionDemo.self, &propertyCount)
for i in                

Блоги

Рекомендаций по книгам