Эффекты и зависимости
Обрабатывайте побочные эффекты и внедряйте зависимости.
«Эффекты и зависимости» — бесплатный урок Swift Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.
Зачем нужны эффекты
Редукторы должны оставаться чистыми: получив состояние и действие, они изменяют только состояние. Но реальным приложениям нужны побочные эффекты, такие как сетевые запросы и таймеры. TCA представляет их как эффекты, возвращаемые редуктором, благодаря чему изменения остаются чистыми, а асинхронная работа выполняется отдельно.
Именно такое разделение делает компоненты пригодными для тестирования.
import ComposableArchitecture
// Reducer stays pure; side effects are values it returns.
// Returning .none means no further work.Возврат .none
Если действие только изменяет состояние и не требует дальнейшей работы, редуктор возвращает .none. Это самый распространённый эффект, означающий отсутствие асинхронной работы.
Вы уже видели его: это базовый вариант, к которому возвращается каждая ветвь.
import ComposableArchitecture
Reduce { state, action in
switch action {
case .incrementTapped:
state.count += 1
return .none
}
}Effect.run для асинхронной работы
Чтобы выполнить асинхронную работу, верните .run. Его замыкание получает дескриптор send, с помощью которого результаты передаются обратно в систему как новые действия.
Здесь действие fetch загружает данные и после завершения отправляет действие ответа.
import ComposableArchitecture
Reduce { state, action in
switch action {
case .reloadTapped:
return .run { send in
let value = try await loadNumber()
await send(.responseReceived(value))
}
case let .responseReceived(value):
state.count = value
return .none
}
}Возврат результатов в виде действий
Эффект не может напрямую изменять состояние; вместо этого он отправляет действия через send. Затем редуктор обрабатывает эти действия и обновляет состояние. Благодаря этому циклу все изменения остаются внутри редуктора.
Таким образом, сетевой ответ превращается в действие ответа, которое редуктор сохраняет.
import ComposableArchitecture
.run { send in
let result = try await fetch()
await send(.dataLoaded(result))
}
// .dataLoaded is then handled by the reducer to set state.Обработка ошибок в эффектах
Асинхронная работа может завершиться с ошибкой. Вы можете перехватить ошибки внутри эффекта и отправить действие сбоя, чтобы редуктор корректно перевёл состояние в состояние ошибки.
Необязательное замыкание catch у .run получает возникшую ошибку.
import ComposableArchitecture
.run { send in
let value = try await loadNumber()
await send(.responseReceived(value))
} catch: { error, send in
await send(.loadFailed)
}Почему важны зависимости
Если редуктор напрямую вызывает сетевой клиент или считывает дату, его становится трудно тестировать и контролировать. TCA решает эту проблему с помощью зависимостей — внедряемых служб, к которым редуктор обращается через @Dependency.
В тестах их можно заменить предсказуемыми имитациями.
import ComposableArchitecture
// Instead of calling APIClient.shared directly,
// inject it as a dependency so tests can replace it.Обёртка свойства @Dependency
Внутри редуктора зависимость объявляют с помощью @Dependency, указывая её ключевой путь в значениях зависимостей. Библиотека предоставляет встроенные зависимости, например clock и генератор идентификаторов UUID.
Здесь редуктор получает зависимость continuousClock.
import ComposableArchitecture
@Reducer
struct TimerFeature {
@ObservableState
struct State: Equatable { var seconds = 0 }
enum Action { case start, tick }
@Dependency(\.continuousClock) var clock
var body: some ReducerOf<Self> {
Reduce { state, action in .none }
}
}Использование зависимости в эффекте
После объявления зависимость можно использовать внутри .run. Поскольку clock внедряется извне, в тестах можно передать тестовую реализацию clock, которая продвигает время по требованию.
Здесь таймер срабатывает каждую секунду с использованием внедрённой зависимости clock.
import ComposableArchitecture
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .start:
return .run { send in
for await _ in self.clock.timer(interval: .seconds(1)) {
await send(.tick)
}
}
case .tick:
state.seconds += 1
return .none
}
}
}Определение собственной зависимости
Пользовательские зависимости регистрируют, сопоставляя значение с DependencyKey и предоставляя liveValue. Затем в DependencyValues добавляют вычисляемое свойство, чтобы зависимость получила ключевой путь.
Здесь клиент программного интерфейса становится полноценной зависимостью.
import ComposableArchitecture
struct NumberClient {
var fetch: () async throws -> Int
}
extension NumberClient: DependencyKey {
static let liveValue = NumberClient {
try await loadNumber()
}
}
extension DependencyValues {
var numberClient: NumberClient {
get { self[NumberClient.self] }
set { self[NumberClient.self] = newValue }
}
}Внедрение пользовательской зависимости
После добавления ключевого пути редуктор объявляет клиент с помощью @Dependency(\.numberClient) и вызывает его внутри эффектов. В рабочей среде используется активное значение, а в тестах его можно переопределить.
Здесь действие перезагрузки получает данные через внедрённый клиент.
import ComposableArchitecture
@Reducer
struct NumberFeature {
@ObservableState
struct State: Equatable { var value = 0 }
enum Action { case reload, response(Int) }
@Dependency(\.numberClient) var numberClient
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .reload:
return .run { send in
let n = try await numberClient.fetch()
await send(.response(n))
}
case let .response(n):
state.value = n
return .none
}
}
}
}Отмена эффектов
Длительные эффекты, например таймеры, должны поддерживать отмену. Вы помечаете эффект идентификатором cancellable, а позже отменяете его из другого действия с помощью .cancel.
Здесь таймер можно остановить по требованию.
import ComposableArchitecture
enum CancelID { case timer }
// Start a cancellable effect:
.run { send in /* ... */ }
.cancellable(id: CancelID.timer)
// Stop it from another action:
.cancel(id: CancelID.timer)Быстрая проверка: эффекты и результаты
Вспомните, как эффект передаёт свой результат обратно редьюсеру.
Итоги: эффекты и зависимости
Вы узнали, как TCA обрабатывает побочные эффекты и внедрение зависимостей:
- Редьюсеры остаются чистыми и возвращают значения
Effect;.noneозначает отсутствие работы. .runвыполняет асинхронную работу и передаёт результаты обратно черезawait send(.action).- Ошибки обрабатываются в замыкании
catchэффекта. @Dependencyвнедряет такие службы, как часы или собственные клиенты, объявленные черезDependencyKeyиDependencyValues.- Длительные эффекты можно сделать
cancellableпо идентификатору.
Далее вы научитесь объединять функциональности и исчерпывающе проверять их.
import ComposableArchitecture
// Recap: pure reducer + Effect.run for async work
// + @Dependency for testable, injectable services.Часто задаваемые вопросы
Урок «Эффекты и зависимости» бесплатный?
Да — полный текст урока «Эффекты и зависимости» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.
Чему я научусь в уроке «Эффекты и зависимости»?
Обрабатывайте побочные эффекты и внедряйте зависимости. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Swift Academy?
Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Эффекты и зависимости»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Swift Academy?
Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Состояние, действие и редьюсер
- Хранилище и интеграция со SwiftUI
- Эффекты и зависимости
- Композиция и тестирование возможностей