Notice
Recent Posts
Recent Comments
Link
๊ด€๋ฆฌ ๋ฉ”๋‰ด

lgvv98

[TCA] Effect #2 (Cancellation) ๋ณธ๋ฌธ

apple/๐Ÿฆฅ TCA

[TCA] Effect #2 (Cancellation)

๐Ÿฅ• ์บ๋Ÿฟ๋งจ 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
Comments