Avatar ·

Как получить ссылку на AppDelegate в Swift: UIApplication.shared.delegate и альтернативы

Всем привет! Я только начал изучать Swift и разработку под iOS, и столкнулся с проблемой. Мне нужно получить доступ к некоторым данным и методам, которые хранятся в моем AppDelegate, из других контроллеров и классов. Я знаю, что AppDelegate — это синглтон, создаваемый системой, но не понимаю, как именно получить на него ссылку.

Вот упрощенная версия моего AppDelegate:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?
    var userToken: String? // Токен авторизации, который нужен во многих контроллерах
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        userToken = "some_token_123"
        return true
    }
    
    func someCustomMethod() {
        print("Метод AppDelegate")
    }
}

Теперь, в одном из моих ViewController'ов, я пытаюсь получить доступ к userToken и вызвать someCustomMethod(). Я пробовал писать что-то вроде let appDelegate = AppDelegate(), но это создает новый экземпляр, а не ссылается на существующий. Получаю nil или пустые данные.

Подскажите, как правильно получить ссылку на уже существующий экземпляр AppDelegate, который создала система при запуске приложения? И есть ли какие-то современные или более безопасные способы сделать это в Swift (например, через UIApplication или другие паттерны)?

Заранее спасибо!

<<>>

Основной способ: UIApplication.shared.delegate

Самый распространенный и прямой способ получить ссылку на AppDelegate — использовать синглтон UIApplication.shared и его свойство delegate. Так как ваш AppDelegate — это именно делегат приложения, вы можете получить к нему доступ следующим образом:

// В любом месте вашего кода (например, в ViewController)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
    // Теперь у вас есть ссылка на реальный экземпляр AppDelegate
    print(appDelegate.userToken ?? "Нет токена")
    appDelegate.someCustomMethod()
} else {
    print("Не удалось получить AppDelegate")
}

Важные моменты:

  • Приведение типа (as? AppDelegate) обязательно, так как свойство delegate имеет тип UIApplicationDelegate?. Без приведения вы сможете вызвать только методы протокола, но не ваши кастомные свойства и методы.
  • Проверка на nil через if-let или guard — хорошая практика, чтобы избежать неожиданных крашей.
  • Этот код работает в любой точке приложения: в контроллерах, вьюхах, сервисах и т.д.

Почему нельзя создавать новый экземпляр?

Как вы правильно заметили, let appDelegate = AppDelegate() создает новый объект, который не имеет связи с системным AppDelegate. У него не будет инициализированных свойств (например, userToken будет nil), и он не будет обрабатывать события жизненного цикла приложения. AppDelegate — это синглтон, управляемый UIApplication.

Современный подход: свойство в SceneDelegate (iOS 13+)

Начиная с iOS 13, Apple частично перенесла ответственность за управление жизненным циклом окна в SceneDelegate. Если ваше приложение использует SceneDelegate (что характерно для новых проектов), вы можете хранить общие данные там:

// В SceneDelegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    var userToken: String?
    
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        userToken = "token_from_scene"
    }
}

// Получение доступа
if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate {
    print(sceneDelegate.userToken ?? "")
}

Однако, если вам нужен именно AppDelegate (например, для обработки push-уведомлений или глубоких ссылок), старый способ остается актуальным.

Более чистая альтернатива: синглтон или сервисный слой

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

class SessionManager {
    static let shared = SessionManager()
    var userToken: String?
    
    private init() {} // Запрещаем создание других экземпляров
}

// Инициализация в AppDelegate
SessionManager.shared.userToken = "token"

// Использование в любом месте
print(SessionManager.shared.userToken ?? "")

Этот подход более гибкий, легко тестируется и не привязывает вас к конкретной архитектуре.


Итог: Для быстрого решения используйте UIApplication.shared.delegate as? AppDelegate. Для долгосрочной поддержки и чистой архитектуры — вынесите общие данные в отдельный синглтон.

<<>>

Дополнительный способ: глобальная переменная

Иногда, для упрощения доступа к AppDelegate в небольших проектах или прототипах, можно объявить глобальную переменную в файле AppDelegate:

// В конце файла AppDelegate.swift, вне класса
let appDelegate = UIApplication.shared.delegate as! AppDelegate

Тогда в любом месте кода вы сможете писать просто appDelegate.userToken. Однако этот подход имеет недостатки:

  • Принудительное извлечение (force unwrap) — если по какой-то причине delegate окажется nil (например, при тестировании), приложение упадет.
  • Глобальное состояние — затрудняет юнит-тестирование и рефакторинг.
  • Не работает, если у вас несколько сцен (SceneDelegate).

Когда это может быть полезно?

Только для очень простых приложений (например, учебные проекты) или когда нужно быстро добавить функциональность без рефакторинга. В production-коде лучше использовать первый основной способ или синглтон-менеджер.

Также, если вы используете SwiftUI, рассмотрите использование @EnvironmentObject для передачи данных через окружение — это более декларативный и современный подход.