apple/TCA

[TCA] Effect #2 (Cancellation)

lgvv 2023. 10. 7. 16:20

[TCA] Effect #1 (Cancellation)

 

목차 

 - 해당 예제와 관련한 설명

 - 해당 예제 코드


# 해당 예제와 관련한 설명

This screen demonstrates how one can cancel in-flight effects in the Composable Architecture.
  
  Use the stepper to count to a number, and then tap the "Number fact" button to fetch a random fact about that number using an API.
  
  While the API request is in-flight, you can tap "Cancel" to cancel the effect and prevent it from feeding data back into the application. Interacting with the stepper while a request is in-flight will also cancel it.

 

이번에는 Effect를 취소하는 방법에 대해서 보자

 

버튼을 눌러 API를 요청에 숫자에 대한 정보를 가져온다.

즉, 플로우를 정리하자면

버튼 클릭 -> API 요청 -> 이때 취소 -> API 응답을 받지 않음

 

 

# 해당 예제 코드

 

// MARK: - Feature domain

struct EffectsCancellation: Reducer {
    struct State: Equatable {
        var count = 0
        var currentFact: String?
        var isFactRequestInFlight = false
    }
    
    enum Action: Equatable {
        case cancelButtonTapped
        case stepperChanged(Int)
        case factButtonTapped
        case factResponse(TaskResult<String>)
    }
    
    @Dependency(\.factClient) var factClient
    private enum CancelID { case factRequest }
    
    func reduce(into state: inout State, action: Action) -> Effect<Action> {
        switch action {
        case .cancelButtonTapped:
            state.isFactRequestInFlight = false
            return .cancel(id: CancelID.factRequest) // factRequest 캔슬
            
        case let .stepperChanged(value):
            state.count = value
            state.currentFact = nil
            state.isFactRequestInFlight = false
            return .cancel(id: CancelID.factRequest) // factRequest 캔슬
            
        case .factButtonTapped:
            state.currentFact = nil
            state.isFactRequestInFlight = true
            
            return .run { [count = state.count] send in
                await send(.factResponse(TaskResult { try await self.factClient.fetch(count) }))
            }
            .cancellable(id: CancelID.factRequest) // 캔슬 등록 Combine의 store(in: $cancellables) 방식과 동일
            
        case let .factResponse(.success(response)):
            state.isFactRequestInFlight = false
            state.currentFact = response
            return .none
            
        case .factResponse(.failure):
            state.isFactRequestInFlight = false
            return .none
        }
    }
}

// MARK: - Feature view

struct EffectsCancellationView: View {
    let store: StoreOf<EffectsCancellation>
    @Environment(\.openURL) var openURL
    
    var body: some View {
        WithViewStore(self.store, observe: { $0 }) { viewStore in
            Form {
                Section {
                    AboutView(readMe: readMe)
                }
                
                Section {
                    Stepper(
                        "\(viewStore.count)",
                        value: viewStore.binding(get: \.count, send: EffectsCancellation.Action.stepperChanged)
                    )
                    
                    if viewStore.isFactRequestInFlight {
                        HStack {
                            Button("Cancel") { viewStore.send(.cancelButtonTapped) }
                            Spacer()
                            ProgressView()
                                .id(UUID()) // SwiftUI 버그로 보이는데 id 없으면 두번째 트리거 시점부터는 보이지 않는 이슈
                        }
                    } else {
                        Button("Number fact") { viewStore.send(.factButtonTapped) }
                            .disabled(viewStore.isFactRequestInFlight)
                    }
                    
                    viewStore.currentFact.map {
                        Text($0).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")
    }
}

// MARK: - SwiftUI previews

struct EffectsCancellation_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            EffectsCancellationView(
                store: Store(initialState: EffectsCancellation.State()) {
                    EffectsCancellation()
                }
            )
        }
    }
}

 

Combine을 주로 사용하고 있는데 TCA에서 사용하는 이런 개념들이 정말 쏙쏙 이해가고 잘 만들었다는 생각이 든다!!

 

 

'apple > TCA' 카테고리의 다른 글

[TCA] Effect #4 (Refreshable)  (0) 2023.10.07
[TCA] Effect #3 (LongLiving)  (0) 2023.10.07
[TCA] Effect #1 (Basics)  (1) 2023.10.07
[TCA] SharedState  (1) 2023.09.27
[TCA] OptionalState (IfLetCase)  (0) 2023.09.27