[SwiftUI][TCA] TCA Case Studies - 03. Effects LongLiving

별똥별·2025년 2월 17일

TCA

목록 보기
22/24

🚀 TCA에서 Long-Living Effect 사용하기 – 스크린샷 이벤트 예제

안녕하세요, 별똥별🌠입니다!
이번 포스트에서는 TCA를 활용해 Long-Living Effect를 어떻게 구현하는지 살펴봅니다.
이 예제에서는 뷰가 나타날 때 스크린샷 알림(Notification)을 비동기적으로 지속적으로 수신하고, 해당 알림이 올 때마다 상태를 업데이트하여 스크린샷 횟수를 카운트합니다


🧐 개념 정리

Long-Living Effect란?

  • 일반적인 Effect는 한 번 실행되고 종료되지만, Long-Living Effect는 뷰가 존재하는 동안 계속 실행되어 외부 이벤트(예: Notification, Timer 등)를 지속적으로 수신합니다.
  • TCA에서는 .run { ... } Effect와 함께 비동기 반복문(for await)을 사용하여 이러한 장기 실행 효과를 쉽게 구현할 수 있습니다.

Cancellation

  • Long-Living Effect는 뷰가 사라지거나 다른 조건에 따라 중단할 수 있지만, 여기서는 특별한 취소 로직 없이 뷰가 남아있는 한 계속 실행됩니다.

🎯 코드 분석

1️⃣ Reducer: LongLivingEffects

@Reducer
struct LongLivingEffects {
    @ObservableState
    struct State: Equatable {
        var screenshotCount: Int = 0
    }
    
    enum Action {
        case task
        case userDidTaskScreenshotNotification
    }
    
    @Dependency(\.screenshots) var screenshots
    
    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .task:
                // 뷰가 나타나면, 스크린샷을 찍을 때마다 Effect를 통해 알림을 보냅니다.
                return .run { send in
                    for await _ in await self.screenshots() {
                        await send(.userDidTaskScreenshotNotification)
                    }
                }
                
            case .userDidTaskScreenshotNotification:
                state.screenshotCount += 1
                return .none
            }
        }
    }
}

주요 포인트

  • 상태(State)
    • screenshotCount가 스크린샷 횟수를 저장합니다.

  • 액션(Action)
    • .task : 뷰가 나타날 때 Long-Living Effect를 시작하는 액션
    • .userDidTaskScreenshotNotification : 스크린샷 이벤트가 발생할 때마다 전송되는 액션

  • 의존성(Dependency)
    • screenshots : 외부에서 주입받은 스크린샷 알림 스트림을 나타냅니다.

  • Effect 실행
    • .task 액션에 대해 .run Effect 내에서 for await 반복문으로 screenshots() 스트림을 구독합니다.
    • 스크린샷 알림이 올 때마다 .userDidTaskScreenshotNotification 액션을 보내고, 상태의 screenshotCount가 증가합니다.

2️⃣ View: LongLivingEffectsView

struct LongLivingEffectsView: View {
    let store: StoreOf<LongLivingEffects>
    
    var body: some View {
        Form {
            Section {
                AboutView(readMe: readMe)
            }
            
            Text("A screenshot of this screen has been taken \(store.screenshotCount) times.")
            
            Section {
                NavigationLink {
                    detailView
                } label: {
                    Text("Navigate to anhoter screen.")
                }
            }
        }
        .navigationTitle("Long-living effects")
        .task { await store.send(.task).finish() }
    }
    
    var detailView: some View {
        Text(
            """
            Take a screenshot of this screen a few times, and then go back to the previous screen to see \
            that those screenshots were not counted.
            """
        )
        .padding(.horizontal, 64)
        .navigationBarTitleDisplayMode(.inline)
    }
}

주요 포인트

  • 뷰 구성
    • Form 내부에 스크린샷 횟수를 표시하는 Text와, 다른 화면으로 이동할 수 있는 NavigationLink가 있습니다.

  • Long-Living Effect 시작
    • .task { await store.send(.task).finish() }를 사용하여 뷰가 나타날 때 .task 액션을 보내고, 이에 따라 Long-Living Effect가 시작됩니다.

  • 상태 업데이트
    • 스크린샷 이벤트 발생 시 screenshotCount가 업데이트되어 UI에 반영됩니다.

3️⃣ 의존성 주입: Screenshots Dependency

extension DependencyValues {
    var screenshots: @Sendable () async -> any AsyncSequence<Void, Never> {
        get { self[ScreenshotsKey.self] }
        set { self[ScreenshotsKey.self] = newValue }
    }
}

private enum ScreenshotsKey: DependencyKey {
    static let liveValue: @Sendable () async -> any AsyncSequence<Void, Never> = {
        NotificationCenter.default
            .notifications(named: UIApplication.userDidTakeScreenshotNotification)
            .map { _ in }
    }
}

주요 포인트

  • 의존성 값(DependencyValues)
    • screenshots 키를 통해 스크린샷 관련 알림 스트림을 제공합니다.

  • liveValue 구현
    • NotificationCenter를 통해 UIApplication.userDidTakeScreenshotNotification 알림을 수신하고, 이를 Void 스트림으로 변환합니다.

  • 이 방식으로 TCA Reducer에서 스크린샷 이벤트를 비동기적으로 수신할 수 있습니다.

📊 개념도 요약

  1. 뷰가 나타나면 .task 액션을 전송 → Long-Living Effect 시작
  2. Effect 내에서 screenshots() 스트림을 구독 → 스크린샷 이벤트가 발생하면 .userDidTaskScreenshotNotification 전송
  3. Reducer가 .userDidTaskScreenshotNotification 액션을 받아 screenshotCount를 증가
  4. UI는 이 상태 변화를 반영하여 스크린샷 횟수를 업데이트

✅ 정리

  • Long-Living Effect는 뷰가 존재하는 동안 지속적으로 외부 이벤트(여기서는 스크린샷 알림)를 수신하여 상태를 업데이트할 수 있게 해줍니다.
  • TCA의 .run { ... }for await를 활용해 비동기 스트림을 처리하며, 이를 통해 장기 실행 효과를 쉽게 구현할 수 있습니다.
  • 의존성 주입(Dependency)을 통해 NotificationCenter와 같은 시스템 이벤트를 손쉽게 -0 TCA 환경으로 끌어올 수 있습니다.

이 예제를 통해 TCA의 Long-Living Effect와 Cancellation을 비롯한 비동기 효과 처리 개념을 보다 쉽게 이해할 수 있길 바랍니다. Happy Coding!

profile
밍밍

0개의 댓글