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

Как обеспечить общий доступ экранов к одним и тем же зависимостям?

1.8 Middle🔥 191 комментариев
#Архитектура и паттерны

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

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

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

Общий доступ к зависимостям между экранами в iOS-приложении

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

Основные архитектурные паттерны для общего доступа

1. Dependency Injection (Внедрение зависимостей)

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

Пример базового внедрения через конструктор:

protocol DataServiceProtocol {
    func fetchData() -> [String]
}

class DataService: DataServiceProtocol {
    func fetchData() -> [String] {
        return ["Item1", "Item2"]
    }
}

class FirstViewController: UIViewController {
    private let dataService: DataServiceProtocol
    
    // Зависимость внедряется через инициализатор
    init(dataService: DataServiceProtocol) {
        self.dataService = dataService
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let data = dataService.fetchData()
        print(data)
    }
}

// Использование
let dataService = DataService()
let firstVC = FirstViewController(dataService: dataService)
let secondVC = SecondViewController(dataService: dataService) // Тот же экземпляр

2. Использование контейнера зависимостей

Для более сложных приложений я использую контейнеры зависимостей, такие как Swinject, Needle или собственные реализации.

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

class DependencyContainer {
    static let shared = DependencyContainer()
    
    private init() {}
    
    // Регистрация и разрешение зависимостей
    private var services: [String: Any] = [:]
    
    func register<T>(service: T.Type, instance: T) {
        services["\(service)"] = instance
    }
    
    func resolve<T>() -> T {
        guard let service = services["\(T.self)"] as? T else {
            fatalError("Service \(T.self) not registered")
        }
        return service
    }
}

// Регистрация перед использованием
DependencyContainer.shared.register(service: DataServiceProtocol.self, 
                                   instance: DataService())

// Получение в разных экранах
class SomeViewController: UIViewController {
    let dataService: DataServiceProtocol = DependencyContainer.shared.resolve()
}

3. Coordinator Pattern с передачей зависимостей

Координаторы (Coordinators) идеально подходят для управления потоками навигации и общими зависимостями.

class AppCoordinator {
    private let window: UIWindow
    private let dataService: DataServiceProtocol
    private let authService: AuthServiceProtocol
    
    init(window: UIWindow) {
        self.window = window
        self.dataService = DataService()
        self.authService = AuthService()
    }
    
    func start() {
        if authService.isAuthenticated {
            showMainFlow()
        } else {
            showAuthFlow()
        }
    }
    
    private func showMainFlow() {
        let mainCoordinator = MainCoordinator(
            window: window,
            dataService: dataService, // Передача общей зависимости
            authService: authService
        )
        mainCoordinator.start()
    }
}

class MainCoordinator {
    private let dataService: DataServiceProtocol
    private let authService: AuthServiceProtocol
    
    init(dataService: DataServiceProtocol, authService: AuthServiceProtocol) {
        self.dataService = dataService
        self.authService = authService
    }
    
    func showProfileScreen() {
        let profileVC = ProfileViewController(
            dataService: dataService, // Та же зависимость
            authService: authService
        )
        // Навигация к экрану
    }
}

4. Environment Object (в SwiftUI)

В SwiftUI есть встроенный механизм для общих зависимостей — @EnvironmentObject.

class AppSettings: ObservableObject {
    @Published var userSession: UserSession?
    @Published var theme: Theme = .light
}

struct ContentView: View {
    @EnvironmentObject var settings: AppSettings
    
    var body: some View {
        if settings.userSession != nil {
            MainView()
        } else {
            LoginView()
        }
    }
}

struct MainView: View {
    @EnvironmentObject var settings: AppSettings // Тот же объект
    
    var body: some View {
        Text("Welcome")
            .foregroundColor(settings.theme.textColor)
    }
}

// В точке входа
ContentView().environmentObject(AppSettings())

5. Service Locator (Локатор служб)

Паттерн Service Locator является альтернативой DI, хотя и менее предпочтителен из-за скрытия зависимостей.

class ServiceLocator {
    static let shared = ServiceLocator()
    private var services: [String: Any] = [:]
    
    func addService<T>(_ service: T) {
        let key = "\(T.self)"
        services[key] = service
    }
    
    func getService<T>() -> T {
        let key = "\(T.self)"
        guard let service = services[key] as? T else {
            fatalError("Service \(T.self) not found")
        }
        return service
    }
}

Критерии выбора подхода

  • Для простых приложений: Достаточно передачи зависимостей через инициализаторы
  • Для средних проектов: Coordinator Pattern + Dependency Injection
  • Для сложных enterprise-приложений: Контейнер зависимостей (Swinject, Needle) с четким разделением слоев
  • Для SwiftUI проектов: @EnvironmentObject в сочетании с внедрением через инициализатор для дочерних объектов

Практические рекомендации

  1. Всегда используйте протоколы для абстракции зависимостей
  2. Избегайте синглтонов как глобального доступа — они затрудняют тестирование
  3. Внедряйте зависимости на уровне координаторов или родительских объектов
  4. Разделяйте зависимости на:
    • Infrastructure (сетевые слои, базы данных)
    • Domain (бизнес-логика)
    • Presentation (специфичные для UI)

Пример реальной структуры

// Уровень инфраструктуры
protocol NetworkServiceProtocol {
    func request<T: Decodable>(_ endpoint: Endpoint) async throws -> T
}

// Уровень домена
protocol UserRepositoryProtocol {
    func fetchUser() async throws -> User
}

// Реализация
class DefaultUserRepository: UserRepositoryProtocol {
    private let networkService: NetworkServiceProtocol
    
    init(networkService: NetworkServiceProtocol) {
        self.networkService = networkService
    }
    
    func fetchUser() async throws -> User {
        try await networkService.request(.userProfile)
    }
}

// Внедрение в экран
class ProfileViewModel {
    private let userRepository: UserRepositoryProtocol
    
    init(userRepository: UserRepositoryProtocol) {
        self.userRepository = userRepository
    }
    
    func loadUser() async {
        do {
            let user = try await userRepository.fetchUser()
            // Обновление UI
        } catch {
            // Обработка ошибок
        }
    }
}

Итог: Единого универсального решения нет — выбор зависит от сложности проекта, команды и долгосрочных требований. Однако принцип явного внедрения зависимостей через абстракции остается фундаментальным для создания поддерживаемых и тестируемых iOS-приложений.

Как обеспечить общий доступ экранов к одним и тем же зависимостям? | PrepBro