apple/TCA

[TCA] Effect #3 (LongLiving)

lgvv 2023. 10. 7. 17:16

[TCA] Effect #1 (LongLiving)

 

목차

 - LongLiving에 대한 설명

 - Effect LongLiving 예제 살펴보기

 

# LongLiving에 대한 설명

이번에는 NotificationCenter의 알림들처럼 effect의 라이프사이클이 긴 것들을 처리하는 방법과 그것들을 View의 라이프 사이클과 연결하는 방법에 대해서 알아보고자 함.

 

이번에는 스크린샷을 여러번 찍으면서 UI의 카운트가 몇번이나 발생하는지 관찰하는 예제

  
  그런 다음 다른 화면으로 이동하여 스크린샷을 촬영한 후 이 화면에서 스크린샷이 *not* 카운트되는지 확인하고, 해당 화면을 떠나면 노티피케이션 효과가 자동으로 취소되고 화면에 들어갈 때 다시 시작.

 

 

# Effect LongLiving 예제 살펴보기

import ComposableArchitecture
import SwiftUI
import XCTestDynamicOverlay

// MARK: - Feature domain

struct LongLivingEffects: Reducer {
    struct State: Equatable {
        var screenshotCount = 0
    }
    
    enum Action: Equatable {
        case task
        case userDidTakeScreenshotNotification
    }
    
    @Dependency(\.screenshots) var screenshots
    
    func reduce(into state: inout State, action: Action) -> Effect<Action> {
        switch action {
        case .task:
            // 뷰 onAppear 타이밍부터 스크린샷을 찍을 때 발생하는 effect 시작
            return .run { send in
                for await _ in await self.screenshots() {
                    await send(.userDidTakeScreenshotNotification)
                }
            }
            
        case .userDidTakeScreenshotNotification:
            state.screenshotCount += 1
            return .none
        }
    }
}

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

private enum ScreenshotsKey: DependencyKey {
    // 이런 방식으로 노티피케이션을 등록할 수 있음.
    static let liveValue: @Sendable () async -> AsyncStream<Void> = {
        await AsyncStream(
            NotificationCenter.default
                .notifications(named: UIApplication.userDidTakeScreenshotNotification)
                .map { _ in }
        )
    }
    static let testValue: @Sendable () async -> AsyncStream<Void> = unimplemented(
        #"@Dependency(\.screenshots)"#, placeholder: .finished
    )
}

// MARK: - Feature view

struct LongLivingEffectsView: View {
    let store: StoreOf<LongLivingEffects>
    
    var body: some View {
        WithViewStore(self.store, observe: { $0 }) { viewStore in
            Form {
                Section {
                    AboutView(readMe: readMe)
                }
                
                Text("A screenshot of this screen has been taken \(viewStore.screenshotCount) times.")
                    .font(.headline)
                
                Section {
                    NavigationLink(destination: self.detailView) {
                        Text("Navigate to another screen")
                    }
                }
            }
            .navigationTitle("Long-living effects")
            .task { await viewStore.send(.task).finish() }  // finish는 작업이 완료될 때까지 대기
            // finish가 없을 경우에는 binding 중첩과 동일한 현상 발생
            // NavigationLink로 뷰를 아예 다 덮어 쓴 경우에는 task가 외부에서 컨트롤 하더라도 detailView에서 스크린 샷 액션이 불리지 않음
        }
    }
    
    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)
    }
}

// MARK: - SwiftUI previews

struct EffectsLongLiving_Previews: PreviewProvider {
    static var previews: some View {
        let appView = LongLivingEffectsView(
            store: Store(initialState: LongLivingEffects.State()) {
                LongLivingEffects()
            }
        )
        
        return Group {
            NavigationView { appView }
            NavigationView { appView.detailView }
        }
    }
}

 

 

시뮬레이터로 테스트하려면 Device - Trigger Screenshot을 눌러야 확인할 수 있다.

NotificationCenter를 해당 TCA에서 사용하는 방법에 대해서 이런 방식으로도 컨트롤 할 수 있구나를 알게 되었는데, 해당 부분은 쿠링 프로젝트에 적용하면서 어떻게 구성할지 더 고민해보자!

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

[TCA] Effect #5 (Timers)  (0) 2023.10.07
[TCA] Effect #4 (Refreshable)  (0) 2023.10.07
[TCA] Effect #2 (Cancellation)  (0) 2023.10.07
[TCA] Effect #1 (Basics)  (1) 2023.10.07
[TCA] SharedState  (1) 2023.09.27