Настройка архитектуры The Composable Architecture (TCA) для iOS-приложения

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1735 услуг
Настройка архитектуры The Composable Architecture (TCA) для iOS-приложения
Сложный
~3-5 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    495

Настройка архитектуры The Composable Architecture (TCA) для iOS-приложения

TCA от Point-Free — не просто ещё один способ расположить папки в Xcode. Это строгий однонаправленный поток данных, где состояние всего приложения изменяется только через Reducer, а каждое изменение тестируется детерминированно. Если вы работаете с SwiftUI, сложной навигацией и большой командой — TCA даёт инструментарий, которого нет у MVVM.

Основные концепции в коде

Store, State, Action, Reducer, Effect — пять китов TCA.

State — структура, описывающая всё, что нужно экрану. Action — enum с ассоциированными значениями, описывающий всё, что может произойти. Reducer — чистая функция (State, Action) -> Effect<Action>. Effect — обёртка над асинхронной работой (сеть, таймеры, MotionManager).

@Reducer
struct ProfileFeature {
    @ObservableState
    struct State: Equatable {
        var user: UserProfile?
        var isLoading = false
        var errorMessage: String?
    }

    enum Action {
        case loadProfile(id: String)
        case profileLoaded(Result<UserProfile, Error>)
        case editButtonTapped
    }

    @Dependency(\.userClient) var userClient

    var body: some Reducer<State, Action> {
        Reduce { state, action in
            switch action {
            case let .loadProfile(id):
                state.isLoading = true
                return .run { send in
                    await send(.profileLoaded(
                        Result { try await userClient.fetch(id) }
                    ))
                }
            case let .profileLoaded(.success(user)):
                state.isLoading = false
                state.user = user
                return .none
            case let .profileLoaded(.failure(error)):
                state.isLoading = false
                state.errorMessage = error.localizedDescription
                return .none
            case .editButtonTapped:
                return .none
            }
        }
    }
}

View не содержит логики: store.send(.loadProfile(id: userId)) и store.user — всё взаимодействие через Store.

Composition: вот где TCA реально блестит

Главная сила TCA — scope и composability. Большое приложение собирается из маленьких Reducer-ов:

@Reducer
struct AppFeature {
    struct State {
        var profile = ProfileFeature.State()
        var feed = FeedFeature.State()
    }
    enum Action {
        case profile(ProfileFeature.Action)
        case feed(FeedFeature.Action)
    }
    var body: some Reducer<State, Action> {
        Scope(state: \.profile, action: \.profile) { ProfileFeature() }
        Scope(state: \.feed, action: \.feed) { FeedFeature() }
    }
}

Каждый модуль разрабатывается независимо. ProfileFeature ничего не знает о FeedFeature. Это позволяет разбить команду на изолированные потоки разработки.

Dependency system — замена синглтонам

TCA поставляется с DependencyValues — механизмом инъекции зависимостей, который заменяет URLSession.shared и UserDefaults.standard в продакшене на тестовые заглушки. Это не Service Locator: зависимости объявляются явно через @Dependency(\.userClient).

extension DependencyValues {
    var userClient: UserClient {
        get { self[UserClientKey.self] }
        set { self[UserClientKey.self] = newValue }
    }
}

В тестах: withDependencies { $0.userClient = .mock } { ... }. Никакого протокола-заглушки, никаких setUp/tearDown с глобальным состоянием.

Тесты: детерминированный TestStore

func test_loadProfile_success() async {
    let store = TestStore(initialState: ProfileFeature.State()) {
        ProfileFeature()
    } withDependencies: {
        $0.userClient.fetch = { _ in .stub(id: "42") }
    }

    await store.send(.loadProfile(id: "42")) {
        $0.isLoading = true
    }
    await store.receive(.profileLoaded(.success(.stub(id: "42")))) {
        $0.isLoading = false
        $0.user = .stub(id: "42")
    }
}

TestStore требует явно описать каждое изменение состояния. Если что-то изменилось, но не описано — тест падает. Это дорогостоящее тестирование писать, но оно полностью исключает регрессии по состоянию.

Навигация в TCA: NavigationStack и tree-based

С TCA 1.x появилась поддержка NavigationStack через StackState/StackAction. Альтернатива — PresentationState/PresentationAction для sheets, alerts, popovers. Все навигационные состояния — часть State, сериализуемы и тестируемы:

@Reducer
struct AppFeature {
    struct State {
        var path = StackState<Path.State>()
    }
    @Reducer enum Path {
        case profile(ProfileFeature)
        case settings(SettingsFeature)
    }
}

Deep link открывается через store.send(.setPath([.profile(...), .settings(...)])). Тест проверяет состояние навигационного стека без UI.

Когда TCA не нужна

Небольшое приложение (5–10 экранов, один разработчик) — TCA добавит бойлерплейт без пропорциональной выгоды. MVVM + Combine или даже @StateObject с сервисами справятся дешевле.

TCA окупается при: команде от 3 человек, сложной навигации с deep links, требовании 80%+ тестового покрытия, фичах с реальным параллелизмом (sync/async эффекты, таймеры, WebSocket).

Что делаем при настройке

Добавляем TCA через Swift Package Manager (swift-composable-architecture актуальная версия). Настраиваем первый Reducer — как образец для команды с покрытием через TestStore. Переносим существующую логику из ViewModel/ViewController в TCA-модули поэкранно.

Обучение команды: разбираем на реальном коде проекта, не на абстрактных примерах.

Сроки

Настройка TCA с нуля + первые 3 экрана с тестами: 5–8 дней. Миграция существующего MVVM-проекта на TCA (10–20 экранов): 3–6 недель. Стоимость — после анализа объёма и текущей архитектуры.