[SwiftUI][TCA] TCA Case Studies - 03. Refreshable

별똥별·2025년 2월 26일

TCA

목록 보기
23/24

🚀 TCA에서 Refreshable Effect 사용하기 – API 호출 예제

안녕하세요, 별똥별🌠입니다!
이번 포스트에서는 TCA를 활용해 Refreshable Effect를 구현하는 방법을 알아보겠습니다.
이 예제에서는 사용자가 화면을 당겨서 새로고침(Pull to Refresh)할 때 API 요청을 보내고, 응답을 받아 화면을 업데이트하는 방식을 다룹니다.


🧐 개념 정리

Refreshable Effect란?

사용자가 데이터를 갱신하고 싶을 때, 흔히 당겨서 새로고침(Pull to Refresh) 기능을 사용합니다. TCA에서는 .run { ... }을 사용하여 비동기 요청을 처리하고, .refreshable 뷰 수정자를 활용해 자연스럽게 적용할 수 있습니다.

Cancellation

API 요청이 오래 걸릴 수 있기 때문에, 사용자가 취소 버튼을 눌러 요청을 중단할 수 있도록 .cancellable(id:)을 적용할 수 있습니다.


🎯 코드 분석

1️⃣ Reducer: Refreshable

@Reducer
struct Refreshable {
    @ObservableState
    struct State: Equatable {
        var count = 0
        var fact: String?
    }
    
    enum Action {
        case cancelButtonTapped
        case decrementButtonTapped
        case factResponse(Result<String, any Error>)
        case incrementButtonTapped
        case refresh
    }
    
    @Dependency(\.factClient) var factClient
    private enum CancelID { case factRequest }
    
    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .cancelButtonTapped:
                return .cancel(id: CancelID.factRequest)
                
            case .decrementButtonTapped:
                state.count -= 1
                return .none
                
            case let .factResponse(.success(fact)):
                state.fact = fact
                return .none
                
            case .factResponse(.failure):
                return .none // 오류 처리 가능
                
            case .incrementButtonTapped:
                state.count += 1
                return .none
                
            case .refresh:
                state.fact = nil
                return .run { [count = state.count] send in
                    await send(
                        .factResponse(Result { try await self.factClient.fetch(count) }),
                        animation: .default
                    )
                }
                .cancellable(id: CancelID.factRequest)
            }
        }
    }
}

주요 포인트

  • 1️⃣ 상태(State)
    • count: 현재 숫자 값.
    • fact: 서버에서 받아올 랜덤한 문장.

  • 2️⃣ 액션(Action)
    • .refresh: 새 데이터를 불러오는 액션.
    • .factResponse: API 응답을 받아 상태를 업데이트.
    • .cancelButtonTapped: 네트워크 요청 취소.
    • .incrementButtonTapped & .decrementButtonTapped: 숫자 증감.

  • 3️⃣ Effect 실행
    • .run 내부에서 factClient.fetch(count) 호출.
    • .cancellable(id:)을 사용하여 요청을 취소 가능하게 함.

2️⃣ View: RefreshableView

struct RefreshableView: View {
    let store: StoreOf<Refreshable>
    @State private var isLoading: Bool = false
    
    var body: some View {
        List {
            Section {
                AboutView(readMe: readMe)
            }
            
            HStack {
                Button {
                    store.send(.decrementButtonTapped)
                } label: {
                    Image(systemName: "minus")
                }
                
                Text("\(store.count)")
                    .monospacedDigit()
                
                Button {
                    store.send(.incrementButtonTapped)
                } label: {
                    Image(systemName: "plus")
                }
            }
            .frame(maxWidth: .infinity)
            .buttonStyle(.borderless)
            
            if let fact = store.fact {
                Text(fact)
                    .bold()
            }
            
            if self.isLoading {
                Button("Cancel") {
                    store.send(.cancelButtonTapped, animation: .default)
                }
            }
        }
        .refreshable {
            isLoading = true
            defer { isLoading = false }
            await store.send(.refresh).finish()
        }
    }
}

주요 포인트

  • 1️⃣ 뷰 구성
    • List 안에 숫자 조작 버튼과 API 응답을 표시하는 Text 포함.
    • .refreshable을 사용해 당겨서 새로고침 지원.

  • 2️⃣ Pull to Refresh
    • 사용자가 화면을 아래로 당기면 .refresh 액션을 호출하여 새로운 데이터를 요청.
    • isLoading 상태를 활용해 로딩 상태 관리.

  • 3️⃣ 취소 버튼
    • API 요청 중 취소할 수 있도록 버튼을 제공.

📊 개념도 요약

  • 1️⃣ 사용자가 화면을 당기면 .refresh 액션 실행
  • 2️⃣ .run 내부에서 factClient.fetch(count) 호출
  • 3️⃣ API 응답이 오면 .factResponse 액션 실행
  • 4️⃣ 상태가 업데이트되면서 화면에 새 데이터 표시
  • 5️⃣ 요청 도중 취소 버튼을 누르면 .cancel(id:)로 요청 취소

✅ 정리

  • TCA에서 .refreshable을 활용해 쉽게 새로고침 기능을 구현할 수 있습니다.
  • .run을 사용해 비동기 API 요청을 관리하며, .cancellable(id:)을 적용해 취소 기능도 추가할 수 있습니다.
  • 사용자가 당겨서 새로고침할 때마다 새로운 데이터를 가져와 UI를 업데이트합니다.

이 예제를 통해 TCA의 Refreshable Effect와 비동기 처리 개념을 쉽게 이해할 수 있길 바랍니다. Happy Coding! 🚀

profile
밍밍

0개의 댓글