
안녕하세요, 별똥별🌠입니다!
이번 포스트에서는 TCA에서 Effect와 Cancellation(취소) 기능을 어떻게 사용하는지 살펴보겠습니다.
특히, 네트워크 요청과 같이 비동기 작업을 실행하는 중에 사용자가 취소 버튼을 누르거나 값이 변경될 때, 실행 중인 Effect를 취소하는 방법을 코드와 함께 자세히 알아봅시다.
Effect
- TCA에서 Effect는 비동기 작업, 지연, 네트워크 요청 등 부수 효과(side effect)를 나타냅니다.
- Reducer 내부에서 .run을 통해 비동기 작업을 수행하고, 그 결과를 액션으로 다시 보내 상태를 업데이트합니다.
Cancellation(취소)
- 장시간 실행되는 Effect가 있을 때, 사용자의 행동(예: 취소 버튼 클릭이나 값 변경)으로 인해 이전에 실행된 Effect를 중단(취소)시킬 수 있습니다.
- TCA는 .cancellable(id:)와 .cancel(id:)를 제공하여 특정 취소 토큰(Identifier)을 통해 실행 중인 Effect를 취소할 수 있도록 합니다.
아래 코드는 카운터 값과 네트워크로 가져온 숫자 팩트를 처리하면서, 사용자가 취소 버튼을 누르거나 Stepper를 조작하면 진행 중인 네트워크 요청 Effect를 취소하는 예제입니다.
코드를 입력하세@Reducer
struct EffectsCancellation {
@ObservableState
struct State: Equatable {
var count = 0
var currentFact: String?
var isFactRequestInFlight = false
}
enum Action {
case cancelButtonTapped
case stepperChanged(Int)
case factButtonTapped
case factResponse(Result<String, any Error>)
}
@Dependency(\.factClient) var factClient
private enum CancelID { case factRequest }
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .cancelButtonTapped:
state.isFactRequestInFlight = false
return .cancel(id: CancelID.factRequest)
case let .stepperChanged(value):
state.count = value
state.currentFact = nil
state.isFactRequestInFlight = false
return .cancel(id: CancelID.factRequest)
case .factButtonTapped:
state.currentFact = nil
state.isFactRequestInFlight = true
return .run { [count = state.count] send in
await send(.factResponse(Result { try await self.factClient.fetch(count) }))
}
.cancellable(id: CancelID.factRequest)
case let .factResponse(.success(response)):
state.isFactRequestInFlight = false
state.currentFact = response
return .none
case .factResponse(.failure):
state.isFactRequestInFlight = false
return .none
}
}
}
}
코드 설명
- Action 및 State 정의
- count : 현재 카운터 값
- currentFact : 네트워크 요청으로 받아온 숫자 팩트
- isFactRequestInFlight : 네트워크 요청 진행 중 여부
- 취소 처리
- .cancelButtonTapped나 .stepperChanged 액션이 발생하면, 진행 중인 Effect를 .cancel(id: CancelID.factRequest)로 취소합니다.
- 네트워크 요청 실행
- .factButtonTapped 액션 시, .run Effect를 통해 factClient.fetch(count) 호출을 실행합니다.
- 이 Effect는 .cancellable(id: CancelID.factRequest)를 붙여서, 동일한 취소 토큰을 통해 나중에 취소할 수 있도록 합니다.
struct EffectsCancellationView: View {
@Bindable var store: StoreOf<EffectsCancellation>
@Environment(\.openURL) var openURL
var body: some View {
Form {
Section {
AboutView(readMe: readMe)
}
Section {
Stepper("\(store.count)", value: $store.count.sending(\.stepperChanged))
if store.isFactRequestInFlight {
HStack {
Button("Cancel") { store.send(.cancelButtonTapped) }
Spacer()
ProgressView()
.id(UUID())
}
} else {
Button("Number Fact") { store.send(.factButtonTapped) }
.disabled(store.isFactRequestInFlight)
}
if let fact = store.currentFact {
Text(fact).padding(.vertical, 8)
}
}
Section {
Button("Number facts provided by numbersapi.com") {
self.openURL(URL(string: "http://numbersapi.com")!)
}
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity)
}
}
.buttonStyle(.borderless)
.navigationTitle("Effect cancellation")
}
}
#Preview {
EffectsCancellationView(store: Store(initialState: EffectsCancellation.State()) {
EffectsCancellation()
}
)
}
뷰 설명
- Stepper
- 사용자가 Stepper를 조작하면 stepperChanged 액션을 보내고, 이에 따라 진행 중인 Effect를 취소합니다.
- Number Fact 버튼
- 요청을 시작하면 factButtonTapped 액션을 보내고, 진행 중이면 ProgressView와 Cancel 버튼을 표시합니다.
- 취소 버튼
- Cancel 버튼을 누르면 cancelButtonTapped 액션이 실행되어 진행 중인 Effect가 취소됩니다.
- 네비게이션 제목 및 AboutView
- 부가 정보 및 외부 링크 버튼도 포함되어 있습니다.
- Action 발생
- 사용자가 버튼을 누르거나 Stepper를 조작 → 해당 액션 발생
- Reducer에서 Effect 실행
- 예를 들어, .factButtonTapped가 발생하면 네트워크 요청을 위한 Effect 실행
이 Effect는 .cancellable(id: CancelID.factRequest)로 태그됨- Effect Cancellation
- 사용자가 취소 버튼을 누르거나 값이 변경되면, .cancel(id: CancelID.factRequest)가 실행되어 진행 중인 Effect가 취소됨
- 상태 업데이트
- 네트워크 요청 성공/실패에 따라 currentFact와 isFactRequestInFlight 상태가 업데이트됨
- TCA에서는 비동기 작업을 Effect로 실행하며, .cancellable(id:)를 통해 해당 작업을 취소할 수 있습니다.
- Reducer에서 특정 액션(취소 버튼 또는 값 변경)이 발생하면, .cancel(id:)를 호출해 진행 중인 Effect를 중단할 수 있습니다.
- UI에서는 진행 중인 작업을 ProgressView로 표시하고, Cancel 버튼을 통해 사용자가 직접 취소할 수 있게 합니다.
이제 여러분도 TCA의 Effect와 Cancellation 기능을 활용하여 보다 유연한 비동기 작업 관리를 구현해보세요!
다음 포스트에서도 TCA의 다양한 활용 사례를 소개할 예정이니 많은 관심 부탁드립니다. 감사합니다! 😊