Как можно отловить вызовы viewDidLoad во всех контроллерах?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Отлов вызовов 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:
- Требуется отслеживание абсолютно всех контроллеров
- Нет контроля над исходным кодом всех контроллеров
- Разрабатывается библиотека или инструмент для мониторинга
Когда использовать Базовый контроллер:
- Есть полный контроль над кодом приложения
- Требуется безопасность и стабильность
- Нужно добавить общую функциональность, а не только отслеживание
Меры предосторожности:
- Всегда вызывайте оригинальную реализацию при swizzling
- Избегайте рекурсивных вызовов
- Тестируйте на различных сценариях (пуш контроллеров, модальные презентации)
- Учитывайте многопоточность при инициализации 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, но его следует использовать с осторожностью и тщательным тестированием. Для проектов, где важна стабильность, лучше выбрать подход с базовым контроллером или комбинацию нескольких методов в зависимости от конкретных требований.