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

Как в SwiftUI и Combine передаешь данные из одного модуля в другой?

2.0 Middle🔥 212 комментариев
#SwiftUI#Архитектура и паттерны

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

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

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

Передача данных между модулями в SwiftUI и Combine

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

1. Через @StateObject / @ObservedObject / @EnvironmentObject

Наиболее распространенный способ при работе с SwiftUI — использование Combine Publishers внутри ObservableObject, которые затем инжектируются в иерархию представлений.

// Модуль А: Создание и управление данными
class DataService: ObservableObject {
    @Published var userData: UserData?
    private var cancellables = Set<AnyCancellable>()
    
    func fetchData() {
        // Логика загрузки данных
        $userData
            .sink { newValue in
                print("Data updated: \(newValue)")
            }
            .store(in: &cancellables)
    }
}

// Модуль Б: Получение данных через @StateObject
struct ModuleAView: View {
    @StateObject private var service = DataService()
    
    var body: some View {
        VStack {
            Text(service.userData?.name ?? "No data")
            ModuleBView()
                .environmentObject(service) // Передача в дочерние модули
        }
    }
}

// Модуль В: Использование через @EnvironmentObject
struct ModuleBView: View {
    @EnvironmentObject var service: DataService
    
    var body: some View {
        Button("Update") {
            service.userData = UserData(name: "Updated")
        }
    }
}

2. Dependency Injection с протоколами

Для тестируемости и соблюдения Dependency Inversion Principle я использую протоколы:

// Общий протокол в отдельном модуле
protocol DataProviderProtocol {
    var currentData: AnyPublisher<DataModel, Never> { get }
    func updateData(_ newData: DataModel)
}

// Реализация в модуле А
class DataProvider: DataProviderProtocol {
    private let dataSubject = CurrentValueSubject<DataModel, Never>(DataModel())
    var currentData: AnyPublisher<DataModel, Never> {
        dataSubject.eraseToAnyPublisher()
    }
    
    func updateData(_ newData: DataModel) {
        dataSubject.send(newData)
    }
}

// Использование в модуле Б через инъекцию
class ModuleBViewModel {
    private let dataProvider: DataProviderProtocol
    private var cancellables = Set<AnyCancellable>()
    
    init(dataProvider: DataProviderProtocol) {
        self.dataProvider = dataProvider
        setupBindings()
    }
    
    private func setupBindings() {
        dataProvider.currentData
            .sink { [weak self] data in
                self?.processData(data)
            }
            .store(in: &cancellables)
    }
}

3. Router/Coordinator с передачей данных

Для навигации между модулями с передачей параметров:

class AppCoordinator: ObservableObject {
    @Published var currentRoute: Route?
    
    enum Route: Hashable {
        case detail(itemId: UUID)
        case settings(config: AppConfig)
    }
    
    func navigateToDetail(with item: DataItem) {
        currentRoute = .detail(itemId: item.id)
    }
}

// В SwiftUI используем NavigationStack с привязкой
struct RootView: View {
    @StateObject private var coordinator = AppCoordinator()
    
    var body: some View {
        NavigationStack(path: $coordinator.routes) {
            HomeView()
                .navigationDestination(for: Route.self) { route in
                    switch route {
                    case .detail(let itemId):
                        DetailView(itemId: itemId)
                    case .settings(let config):
                        SettingsView(config: config)
                    }
                }
        }
        .environmentObject(coordinator)
    }
}

4. Shared Data Containers (например, для модульного приложения)

В случае модульной архитектуры с отдельными Swift Packages:

// Shared модуль с общими моделями и интерфейсами
public struct AppState {
    public var user: User?
    public var settings: Settings
}

public class AppStore {
    public static let shared = AppStore()
    private let stateSubject = CurrentValueSubject<AppState, Never>(AppState())
    public var state: AnyPublisher<AppState, Never> {
        stateSubject.eraseToAnyPublisher()
    }
    
    public func updateUser(_ user: User) {
        var newState = stateSubject.value
        newState.user = user
        stateSubject.send(newState)
    }
}

// В любом модуле
import SharedModule

class FeatureViewModel {
    init() {
        AppStore.shared.state
            .map { $0.user }
            .removeDuplicates()
            .sink { user in
                // Реакция на изменения пользователя
            }
            .store(in: &cancellables)
    }
}

5. NotificationCenter с Combine

Для широковещательных событий между несвязанными модулями:

extension Notification.Name {
    static let dataUpdated = Notification.Name("dataUpdated")
}

class DataEmitter {
    func emitUpdate() {
        NotificationCenter.default.post(
            name: .dataUpdated,
            object: nil,
            userInfo: ["data": newData]
        )
    }
}

class DataReceiver {
    init() {
        NotificationCenter.default.publisher(for: .dataUpdated)
            .compactMap { $0.userInfo?["data"] as? DataModel }
            .sink { data in
                self.handleUpdate(data)
            }
            .store(in: &cancellables)
    }
}

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

  1. Избегайте синглтонов там, где возможна dependency injection
  2. Используйте протоколы для межмодульного взаимодействия
  3. Управляйте жизненным циклом подписок Combine через Set<AnyCancellable>
  4. Тестируйте потоки данных с помощью TestScheduler из Combine
  5. Логируйте ключевые события в цепочках Publisher для отладки

Главный принцип: данные должны течь сверху вниз, события — снизу вверх. Родительские модули владеют источниками данных, дочерние — только читают и отправляют интенты на изменение. Это обеспечивает предсказуемость состояния приложения и упрощает отладку сложных взаимодействий.

Как в SwiftUI и Combine передаешь данные из одного модуля в другой? | PrepBro