0Pricing
Swift Academy · 강의

이펙트와 의존성

부수 효과를 처리하고 의존성을 주입합니다.

이펙트와 의존성은(는) CoddyKit의 무료 Swift Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 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
    }
}

결과를 Action으로 다시 전달하기

이펙트는 상태를 직접 변경할 수 없고, 대신 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.

이펙트에서 오류 처리하기

비동기 작업에서는 오류가 발생할 수 있습니다. 이펙트 내부에서 오류를 포착하고 실패 동작을 보내면 리듀서가 깔끔하게 오류 상태로 전환할 수 있습니다.

.run의 선택적 catch 클로저는 발생한 오류를 받습니다.

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과 고유 식별자 생성기 같은 기본 제공 의존성을 제공합니다.

여기서는 리듀서가 연속적인 clock을 가져옵니다.

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 ID를 지정하고, 나중에 다른 동작에서 .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를 통해 선언합니다.
  • 오래 실행되는 효과는 ID를 사용해 cancellable로 만들 수 있습니다.

다음에는 여러 기능을 함께 구성하고 철저하게 테스트합니다.

import ComposableArchitecture

// Recap: pure reducer + Effect.run for async work
// + @Dependency for testable, injectable services.

자주 묻는 질문

“이펙트와 의존성” 강의는 무료인가요?

네 — “이펙트와 의존성” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Swift Academy 강의 전체를 잠금 해제할 수 있습니다. Swift Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“이펙트와 의존성”에서 뭘 배우나요?

부수 효과를 처리하고 의존성을 주입합니다. 브라우저에서 직접 실행하는 실습 코드로 Swift Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Swift Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Swift Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“이펙트와 의존성” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Swift Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Swift Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 상태, 액션, 리듀서
  2. Store와 SwiftUI 통합
  3. 이펙트와 의존성
  4. 기능 조합과 테스트
← Swift Academy(으)로 돌아가기