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

Как можно отловить вызовы viewDidLoad во всех контроллерах?

2.3 Middle🔥 52 комментариев
#Тестирование и отладка

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

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

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

Отлов вызовов viewDidLoad в iOS-приложении

Отлов вызовов viewDidLoad во всех контроллерах может потребоваться для различных целей: логирования, аналитики, отладки, мониторинга производительности или внедрения общей функциональности. Вот основные подходы к решению этой задачи:

1. Метод Swizzling

Наиболее мощный, но требующий осторожности подход — подмена оригинальной реализации viewDidLoad с помощью method swizzling из Objective-C runtime.

import UIKit

extension UIViewController {
    static let swizzleViewDidLoad: Void = {
        let originalSelector = #selector(UIViewController.viewDidLoad)
        let swizzledSelector = #selector(UIViewController.swizzled_viewDidLoad)
        
        guard let originalMethod = class_getInstanceMethod(UIViewController.self, originalSelector),
              let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledSelector) else {
            return
        }
        
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }()
    
    @objc func swizzled_viewDidLoad() {
        // Вызываем оригинальный viewDidLoad (теперь под именем swizzled_viewDidLoad)
        self.swizzled_viewDidLoad()
        
        // Логируем или выполняем дополнительную логику
        print("✅ viewDidLoad вызван в: \(type(of: self))")
        
        // Можно добавить любую общую логику
        trackViewControllerLifecycle()
    }
    
    private func trackViewControllerLifecycle() {
        // Логика для аналитики или мониторинга
        Analytics.track(event: "view_did_load", params: ["controller": String(describing: type(of: self))])
    }
}

// Активация swizzling при запуске приложения
@objc class AppLifecycleManager: NSObject {
    @objc static func setup() {
        _ = UIViewController.swizzleViewDidLoad
    }
}

// В AppDelegate:
// AppLifecycleManager.setup()

Важные замечания по swizzling:

  • Работает для всех контроллеров в приложении, включая системные
  • Требует аккуратного использования во избежание непредсказуемого поведения
  • Может конфликтовать с другими библиотеками, использующими swizzling
  • Рекомендуется выполнять только один раз при запуске приложения

2. Базовый контроллер приложения

Более безопасный, но менее универсальный подход — создание базового контроллера.

class BaseViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Общая логика для всех контроллеров
        print("🟢 viewDidLoad: \(type(of: self))")
        
        // Настройка аналитики
        setupAnalytics()
    }
    
    private func setupAnalytics() {
        // Общая конфигурация отслеживания
    }
}

// Использование во всех контроллерах приложения:
class ProfileViewController: BaseViewController {
    override func viewDidLoad() {
        super.viewDidLoad() // Вызовет логирование из BaseViewController
        
        // Специфичная логика контроллера
    }
}

Ограничения подхода:

  • Не работает с системными контроллерами (UINavigationController, UITabBarController)
  • Требует наследования всех контроллеров от BaseViewController
  • Не охватывает контроллеры из сторонних библиотек

3. Аспектно-ориентированное программирование (AOP)

Использование библиотек для AOP, таких как Aspects:

import Aspects

// Где-то при запуске приложения
do {
    try UIViewController.aspect_hook(
        #selector(UIViewController.viewDidLoad),
        with: .positionBefore,
        usingBlock: { aspectInfo in
            if let controller = aspectInfo.instance() as? UIViewController {
                print("📊 viewDidLoad: \(type(of: controller))")
            }
        }
    )
} catch {
    print("Ошибка при навешивания аспекта: \(error)")
}

4. UIViewController категория (Objective-C)

Для Objective-C проектов можно использовать категории:

@implementation UIViewController (ViewDidLoadLogging)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(logging_viewDidLoad);
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)logging_viewDidLoad {
    [self logging_viewDidLoad];
    
    NSLog(@"viewDidLoad called for: %@", NSStringFromClass([self class]));
}

@end

Рекомендации по выбору подхода

Когда использовать Swizzling:

  • Требуется отслеживание абсолютно всех контроллеров
  • Нет контроля над исходным кодом всех контроллеров
  • Разрабатывается библиотека или инструмент для мониторинга

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

  • Есть полный контроль над кодом приложения
  • Требуется безопасность и стабильность
  • Нужно добавить общую функциональность, а не только отслеживание

Меры предосторожности:

  1. Всегда вызывайте оригинальную реализацию при swizzling
  2. Избегайте рекурсивных вызовов
  3. Тестируйте на различных сценариях (пуш контроллеров, модальные презентации)
  4. Учитывайте многопоточность при инициализации swizzling

Практическое применение:

// Пример комплексного решения для продакшена
final class ViewControllerTracker {
    static let shared = ViewControllerTracker()
    private var activeViewControllers: Set<String> = []
    
    private init() {
        setupSwizzling()
    }
    
    private func setupSwizzling() {
        _ = UIViewController.swizzleViewDidLoad
    }
    
    func trackViewControllerLoad(_ controller: UIViewController) {
        let controllerName = String(describing: type(of: controller))
        activeViewControllers.insert(controllerName)
        
        // Отправка в аналитику
        Analytics.sendEvent("view_controller_loaded", 
                          properties: ["name": controllerName])
        
        // Мониторинг утечек памяти
        LeakDetector.track(controller)
    }
}

Вывод: Для максимального покрытия всех контроллеров, включая системные, оптимальным является method swizzling, но его следует использовать с осторожностью и тщательным тестированием. Для проектов, где важна стабильность, лучше выбрать подход с базовым контроллером или комбинацию нескольких методов в зависимости от конкретных требований.

Как можно отловить вызовы viewDidLoad во всех контроллерах? | PrepBro