Как работает Swizzling методов в iOS?

«Как работает Swizzling методов в iOS?» — вопрос из категории Swift Core, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Swizzling — это техника подмены реализации метода во время выполнения. Она возможна благодаря динамической природе Objective-C Runtime, который управляет диспетчеризацией сообщений (вызовов методов).

Основные шаги:

  1. Получить Method (структура runtime) для оригинального и нового селекторов.
  2. Добавить новый метод в класс, если его ещё нет.
  3. Обменять реализации (IMP) двух методов.

Пример swizzling viewDidLoad:

import ObjectiveC

extension UIViewController {
    static func swizzleViewDidLoad() {
        // 1. Убедимся, что swizzling выполняется только один раз
        guard self === UIViewController.self else { return }

        let originalSelector = #selector(viewDidLoad)
        let swizzledSelector = #selector(swizzled_viewDidLoad)

        // 2. Получаем объекты Method из runtime
        guard let originalMethod = class_getInstanceMethod(self, originalSelector),
              let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) 
        else { return }

        // 3. Пытаемся добавить новый метод. Если получилось, значит метод не был унаследован.
        let didAddMethod = class_addMethod(self, originalSelector, 
                                          method_getImplementation(swizzledMethod),
                                          method_getTypeEncoding(swizzledMethod))

        if didAddMethod {
            // Метод был добавлен -> подменяем селектор
            class_replaceMethod(self, swizzledSelector,
                               method_getImplementation(originalMethod),
                               method_getTypeEncoding(originalMethod))
        } else {
            // Метод уже существует -> просто меняем реализации местами
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }

    @objc private func swizzled_viewDidLoad() {
        // Новая логика ДО оригинального метода
        print("ViewDidLoad called for: (type(of: self))")

        // Вызов ОРИГИНАЛЬНОЙ реализации (которая теперь находится под селектором swizzled_viewDidLoad)
        self.swizzled_viewDidLoad()

        // Новая логика ПОСЛЕ оригинального метода
    }
}
// Вызов в AppDelegate:
UIViewController.swizzleViewDidLoad()

Важные предостережения:

  • Swizzling нарушает инкапсуляцию и усложняет отладку.
  • Выполняйте его только один раз (например, в +load или в initialize).
  • Избегайте swizzling системных классов, если нет крайней необходимости.
  • В Swift swizzling работает только для классов, наследующих от NSObject, и методов, помеченных @objc dynamic.