Что такое method swizzling в Swift и приведи пример его использования?

Ответ

Method swizzling — это техника подмены реализации метода во время выполнения (runtime) путем обмена указателей на реализации (implementations) двух методов. Используется в крайних случаях для отладки, логирования или добавления функциональности, когда другие подходы (наследование, делегирование, композиция) неприменимы.

Пример: добавление логирования ко всем вызовам viewDidLoad в UIViewController.

import UIKit

extension UIViewController {
    // 1. Объявляем новый метод для подмены
    @objc dynamic func swizzled_viewDidLoad() {
        // 2. Вызываем оригинальную реализацию (теперь она доступна под именем `swizzled_viewDidLoad`)
        swizzled_viewDidLoad()
        // 3. Добавляем свою логику
        print("[Swizzle] ViewDidLoad called for: (type(of: self))")
    }

    // 4. Статический метод для однократного выполнения swizzling
    static func swizzleViewDidLoad() {
        // Гарантируем выполнение только один раз
        guard self === UIViewController.self else { return }

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

        guard 
            let originalMethod = class_getInstanceMethod(self, originalSelector),
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
        else {
            print("Swizzling failed: methods not found")
            return
        }

        // 5. Меняем реализации местами
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

// Вызов в AppDelegate при запуске приложения:
// UIViewController.swizzleViewDidLoad()

Ключевые моменты и best practices:

  • @objc dynamic: Обязателен для методов, участвующих в swizzling, чтобы включить динамическую диспетчеризацию Objective-C runtime.
  • Вызов оригинала: Всегда вызывайте оригинальную реализацию (под новым именем), чтобы не сломать ожидаемое поведение системы или фреймворка.
  • Однократность: Swizzling должен выполняться только один раз, обычно в +load (Objective-C) или в инициализаторе AppDelegate (Swift). В примере используется проверка self === UIViewController.self.
  • Опасности: Может привести к сложно отлаживаемым ошибкам, конфликтам между библиотеками и поломке при обновлениях iOS. Избегайте в продакшн-коде, если есть альтернативы.
  • Альтернативы: Рассмотрите использование делегирования, подклассов, композиции или сабклассинга (например, через method_exchangeImplementations только в своих классах).

Ответ 18+ 🔞

А, ну вот, блядь, метод свёзлинг! Это когда ты такой: «А давайте-ка я, сука, подменю реализацию метода прямо в рантайме, на ходу, как в кино про хакеров». Ну, типа, меняешь указатели на функции местами, и все дела. Используется, когда уже совсем припёрло — для отладки, логирования или воткнуть какую-нибудь фичу, когда нормальные пути (наследование, делегирование) уже в пизду улетели.

Вот, смотри, пример: как прикрутить логирование ко всем вызовам viewDidLoad в UIViewController.

import UIKit

extension UIViewController {
    // 1. Объявляем наш подменный метод
    @objc dynamic func swizzled_viewDidLoad() {
        // 2. Вызываем оригинал (он теперь под этим именем прячется, ёпта)
        swizzled_viewDidLoad()
        // 3. А тут наша дичь — логируем
        print("[Swizzle] ViewDidLoad called for: (type(of: self))")
    }

    // 4. Статический метод, чтобы этот цирк запустить один раз
    static func swizzleViewDidLoad() {
        // Сделаем вид, что мы умные, и не наебём систему дважды
        guard self === UIViewController.self else { return }

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

        guard 
            let originalMethod = class_getInstanceMethod(self, originalSelector),
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
        else {
            print("Swizzling failed: methods not found")
            return
        }

        // 5. Ну и, собственно, сам развод — меняем их местами, как в напёрстках
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

// Запускаем этот аттракцион в AppDelegate при старте:
// UIViewController.swizzleViewDidLoad()

А теперь, блядь, ключевые моменты, чтобы не обосраться:

  • @objc dynamic: Это, сука, обязательно. Без этого Objective-C runtime тебя не увидит, и вся магия накроется медным тазом.
  • Вызов оригинала: Ты ж не мудак? Всегда вызывай оригинальную реализацию (которая теперь под новым именем), а то сломаешь всё, что только можно. Овердохуища проблем потом будет.
  • Один раз, и хватит: Запускать этот трюк нужно один-единственный раз, обычно при старте приложения. У меня в примере проверка self === UIViewController.self как раз от дурака страхует.
  • Опасности, ёпта: Это пиздец как мощно, но и ломает всё с невероятной силой. Могут быть конфликты с другими библиотеками, всё полетит к чертям после обновления iOS. В продакшне лучше не выёбывайся, если есть нормальный путь.
  • Альтернативы: О, их дохуя! Делегирование, подклассы, композиция... Ну или хотя бы свёзлинг делай только в своих классах, а не лезь в системные, как последний распиздяй.