← Назад к вопросам

За счет чего возможен Swizzling?

1.8 Middle🔥 61 комментариев
#Язык Swift

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Механизм Swizzling в Objective-C и Swift

Swizzling — это техника, позволяющая в runtime подменить реализацию метода класса на собственную, не изменяя исходный код. Эта возможность основана на динамической природе Objective-C и частично доступна в Swift для классов, наследующих от NSObject.

Основные механизмы, обеспечивающие Swizzling

1. Динамическая диспетчеризация сообщений (Objective-C Runtime)

В Objective-C вызов метода — это отправка сообщения объекту через функцию objc_msgSend. Реализация метода определяется в таблице диспетчеризации (dispatch table), которая хранит соответствие между селектором (SEL) и имплементацией (IMP). Swizzling манипулирует этой таблицей.

// SEL - указатель на имя метода (например, @selector(viewDidLoad))
// IMP - указатель на функцию: id (*IMP)(id, SEL, ...)

2. Изменение соответствия SEL → IMP

Swizzling подменяет IMP для конкретного SEL, перенаправляя вызовы метода. Ключевые функции Runtime:

// Получить оригинальную IMP метода
Method class_getInstanceMethod(Class cls, SEL name);

// Подменить IMP метода
void method_setImplementation(Method method, IMP imp);

// Поменять IMP двух методов местами
void method_exchangeImplementations(Method m1, Method m2);

3. Структура класса в памяти

Каждый класс в Objective-C содержит objc_class, который включает:

  • isa указатель на метакласс
  • superclass указатель на родительский класс
  • method_cache кэш методов
  • method_list список методов (изменяемый в runtime)

Практическая реализация Swizzling

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

#import <objc/runtime.h>

@implementation UIViewController (CustomSwizzle)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        // Получаем оригинальный и заменяющий селекторы
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(custom_viewWillAppear:);
        
        // Получаем Method структуры
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        // Пытаемся добавить метод (на случай если оригинальный отсутствует)
        BOOL didAddMethod = class_addMethod(class,
                                           originalSelector,
                                           method_getImplementation(swizzledMethod),
                                           method_getTypeEncoding(swizzledMethod));
        
        if (didAddMethod) {
            // Метод был добавлен - заменяем реализацию
            class_replaceMethod(class,
                               swizzledSelector,
                               method_getImplementation(originalMethod),
                               method_getTypeEncoding(originalMethod));
        } else {
            // Метод уже существует - меняем реализации местами
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)custom_viewWillAppear:(BOOL)animated {
    // Вызываем оригинальную реализацию (которая теперь по swizzledSelector)
    [self custom_viewWillAppear:animated];
    
    // Наша кастомная логика
    NSLog(@"View will appear: %@", NSStringFromClass([self class]));
}
@end

Особенности для Swift:

В Swift swizzling работает только для:

  • Классов, наследующих от NSObject
  • Методов, помеченных как @objc dynamic
extension UIViewController {
    @objc dynamic func custom_viewDidLoad() {
        // Вызов оригинальной реализации
        custom_viewDidLoad()
        print("Swizzled viewDidLoad called")
    }
    
    static func swizzleViewDidLoad() {
        let originalSelector = #selector(UIViewController.viewDidLoad)
        let swizzledSelector = #selector(UIViewController.custom_viewDidLoad)
        
        guard let originalMethod = class_getInstanceMethod(self, originalSelector),
              let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
            return
        }
        
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

Критические аспекты и ограничения

Безопасность и порядок выполнения:

  1. Метод +load вызывается автоматически при загрузке класса
  2. dispatch_once гарантирует однократное выполнение
  3. Порядок swizzling важен — несколько библиотек могут конфликтовать

Ограничения:

  • Не работает для pure Swift классов без @objc dynamic
  • Невозможно для C функций и статических методов
  • Потенциальные конфликты при множественном swizzling одного метода
  • Сложность отладки — нарушается стек вызовов

Альтернативные подходы

В современных iOS-приложениях swizzling часто заменяют:

  • Method Injection через категории
  • Delegate-паттерны и композиция
  • Аспектно-ориентированное программирование (AOP) библиотеки
  • Swift Protocol Extensions для полиморфизма

Заключение

Swizzling возможен благодаря динамическому характеру Objective-C Runtime, который позволяет модифицировать таблицу диспетчеризации методов во время выполнения программы. Это мощный, но опасный инструмент, требующий глубокого понимания механизмов работы Objective-C и осторожного использования в production-коде. В современной iOS-разработке его применение оправдано в основном для системных задач: логирования, отслеживания аналитики, или в рамках фреймворков, где модификация исходного кода невозможна.

За счет чего возможен Swizzling? | PrepBro