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

lgvv98

[TCA] Effect #1 (Basics) ๋ณธ๋ฌธ

apple/๐Ÿฆฅ TCA

[TCA] Effect #1 (Basics)

๐Ÿฅ• ์บ๋Ÿฟ๋งจ 2023. 10. 7. 15:57

[TCA] Effect #1 (Basics)

 

๋ชฉ์ฐจ

 - Effect๋ž€?

 - Effect Basic ์˜ˆ์ œ ์‚ดํŽด๋ณด๊ธฐ

 

 - Effect๋ž€?

TCA๋กœ ๋งŒ๋“ค์–ด์ง„ ๊ธฐ๋Šฅ์— Side Effect์„ ๋„์ž…ํ•  ์ˆ˜ ์žˆ์Œ.

Side Effect๋ž€ ์™ธ๋ถ€์—์„œ ์ˆ˜ํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ์ž‘์—…๋“ค๋กœ API ์š”์ฒญ, HTTP๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋“ฑ ๋ถˆํ™•์‹คํ•˜๋ฉฐ ๋ณต์žกํ•˜๊ธฐ๋„ ํ•จ.

 

- ์˜์–ด ์šฉ์–ด ์ •๋ฆฌ
NB: nota bene๋ผ๋Š” ๋ผํ‹ด์–ด๋กœ ์ฃผ์˜, ์œ ์˜๋ผ๋Š” ์˜๋ฏธ

 

 - Effect Basic ์˜ˆ์ œ ์‚ดํŽด๋ณด๊ธฐ

import ComposableArchitecture
import SwiftUI

// MARK: - Feature domain

struct EffectsBasics: Reducer {
    struct State: Equatable {
        var count = 0
        var isNumberFactRequestInFlight = false
        var numberFact: String?
    }
    
    enum Action: Equatable {
        case decrementButtonTapped
        case decrementDelayResponse
        case incrementButtonTapped
        case numberFactButtonTapped
        case numberFactResponse(TaskResult<String>)
    }
    
    @Dependency(\.continuousClock) var clock
    @Dependency(\.factClient) var factClient
    private enum CancelID { case delay }
    
    func reduce(into state: inout State, action: Action) -> Effect<Action> {
        switch action {
        case .decrementButtonTapped:
            state.count -= 1
            state.numberFact = nil
            // ๋งŒ์•ฝ ์ˆ˜๊ฐ€ ์Œ์ˆ˜๋ผ๋ฉด 1์ดˆ ํ›„์— ์ˆ˜๋ฅผ ๋‹ค์‹œ 1 ์žฌ์ฆ๊ฐ€ ์‹œ์ผœ์ฃผ๋Š” ๊ตฌํ˜„
            return state.count >= 0
            ? .none
            : .run { send in
                try await self.clock.sleep(for: .seconds(1)) // 1์ดˆ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€
                await send(.decrementDelayResponse) // 1์ดˆ ๋’ค ๋”œ๋ ˆ์ด
            }
            .cancellable(id: CancelID.delay) // ์ถ”์ธก?: ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•˜๊ณ , ํŠน์ • ์‹œ์ ์— ์ทจ์†Œ ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์ด๋ฒคํŠธ๋ฅผ ์ทจ์†Œ์‹œํ‚ค๊ธฐ ์œ„ํ•˜์—ฌ ๋“ฑ๋ก
            
        case .decrementDelayResponse:
            if state.count < 0 {
                state.count += 1 // ๊ฐ์†Œ ์ด๋ฒคํŠธ๊ฐ€ ๋„์ฐฉํ•˜๋ฉด ์–‘์ˆ˜์ผ ๋•Œ๋งŒ ์˜ฌ๋ฆผ
            }
            return .none
            
        case .incrementButtonTapped:
            state.count += 1
            state.numberFact = nil
            return state.count >= 0
            ? .cancel(id: CancelID.delay) // cancel์„ ํ†ตํ•œ ๋”œ๋ ˆ์ด ๋ฐ”์ธ๋”ฉ ์ œ๊ฑฐ
            : .none
            
        case .numberFactButtonTapped:
            state.isNumberFactRequestInFlight = true
            state.numberFact = nil
            // API๋ฅผ ํ†ตํ•ด ๋ฐ›์•„ ์˜จ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค
            // `numberFactResponse` action์œผ๋กœ value ๋ฐ˜ํ™˜
            return .run { [count = state.count] send in
                // TaskResult๋Š” enum์œผ๋กœ Result์™€ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
                await send(.numberFactResponse(TaskResult { try await self.factClient.fetch(count) }))
            }
            
        case let .numberFactResponse(.success(response)):
            state.isNumberFactRequestInFlight = false
            state.numberFact = response
            return .none
            
        case .numberFactResponse(.failure):
            // ์ฃผ์˜: ๊ฒฝ๊ณ  ์•Œ๋Ÿฟ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ์—๋Ÿฌ๋ฅผ ํ•ธ๋“ค๋ง์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„
            state.isNumberFactRequestInFlight = false
            return .none
        }
    }
}

// MARK: - Feature view

struct EffectsBasicsView: View {
    let store: StoreOf<EffectsBasics>
    @Environment(\.openURL) var openURL // ์™ธ๋ถ€์—์„œ ์ •์˜๋˜์–ด ์žˆ์Œ
    
    var body: some View {
        WithViewStore(self.store, observe: { $0 }) { viewStore in
            Form {
                Section {
                    AboutView(readMe: readMe)
                }
                
                Section {
                    HStack {
                        Button {
                            viewStore.send(.decrementButtonTapped)
                        } label: {
                            Image(systemName: "minus")
                        }
                        
                        Text("\(viewStore.count)")
                            .monospacedDigit()
                        
                        Button {
                            viewStore.send(.incrementButtonTapped)
                        } label: {
                            Image(systemName: "plus")
                        }
                    }
                    .frame(maxWidth: .infinity)
                    
                    Button("Number fact") { viewStore.send(.numberFactButtonTapped) }
                        .frame(maxWidth: .infinity)
                    
                    if viewStore.isNumberFactRequestInFlight {
                        ProgressView()
                            .frame(maxWidth: .infinity)
                        // Swift UI์— ์ƒˆ๋กœ์šด ID๊ฐ€ ์ฃผ์–ด์ง€์ง€ ์•Š์œผ๋ฉด ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ทฐ๊ฐ€ ๋‘๋ฒˆ์งธ์—๋Š” ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š” ๋ฒ„๊ทธ๊ฐ€ ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ํ•จ.
                        // ํ•œ๋ฒˆ์€ ๋‚˜ํƒ€๋‚˜๊ณ  ๋‘๋ฒˆ์€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๋ง
                            .id(UUID())
                    }
                    
                    if let numberFact = viewStore.numberFact {
                        Text(numberFact)
                    }
                }
                
                Section {
                    Button("Number facts provided by numbersapi.com") {
                        self.openURL(URL(string: "http://numbersapi.com")!)
                    }
                    .foregroundStyle(.secondary)
                    .frame(maxWidth: .infinity)
                }
            }
            .buttonStyle(.borderless)
        }
        .navigationTitle("Effects")
    }
}

// MARK: - SwiftUI previews

struct EffectsBasicsView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            EffectsBasicsView(
                store: Store(initialState: EffectsBasics.State()) {
                    EffectsBasics()
                }
            )
        }
    }
}

 

TaskResult์™€ enum Cancel { } ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Œ.
ํŠนํžˆ enum Cancel ๋ถ€๋ถ„์€ ์กฐ๊ธˆ ๋” ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ปจํŠธ๋กค ํ•˜๋Š”๊ฒŒ ํ•„์š”ํ•  ๋“ฏ ์‹ถ๋‹ค

 

'apple > ๐Ÿฆฅ TCA' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[TCA] Effect #3 (LongLiving)  (0) 2023.10.07
[TCA] Effect #2 (Cancellation)  (0) 2023.10.07
[TCA] SharedState  (1) 2023.09.27
[TCA] OptionalState (IfLetCase)  (0) 2023.09.27
[TCA] FocusState  (0) 2023.09.27
Comments