ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TCA에서 AsyncStream 사용해서 타이머 구현하기
    개발/Swift 2025. 1. 15. 23:18

    타이머를 구현 해야 했습니다

    TCA + SwiftUI 기반으로 구현중이라 여기에 걸맞게 구현해야 합니다

    그래서 Reducer에서 타이머를 구현하고 카운트다운 State를 설정하여

    View에서 바인딩하는 방식으로 구현하려 합니다

     

    우선 AsyncStream 사용해본적이 없어서 이거부터 만들어봤습니다

    간단히 60 부터 0까지 반복문 + Task.sleep을 넣어 1초마다 호출되도록 하였습니다

    Task {
       for i in (0...59).reversed() {
           try await Task.sleep(nanoseconds: 1_000_000_000)
           print(i)
       }
    }
    
    // 59..58..57..56
    

    이제 저 i를 AsyncStream 에 담아서 계속 전송해주도록 할겁니다

    private func timerStream() -> AsyncStream<Int> {
             AsyncStream<Int> { continuation in
                 Task {
                     for i in (0...59).reversed() {
                         try await Task.sleep(nanoseconds: 1_000_000_000)
                         continuation.yield(i)
                     }
                     continuation.finish()
                 }
            }
        }
    

    이렇게 되면 yield 를 통해 계속 배출(?)이 되고 60초가 끝나면 finish를 통해 끝이 납니다

    yield에 넣어준 i 가 Int 이므로 AsyncStream<Int> 타입이 됩니다

    이제 AsyncStream<Int> 을 사용해야하는데 for await을 통해 가능합니다

     for await time in timerStream() {
          print(time)
      }
    

    이렇게 1초마다 time이 방출이 되었고 이것을 TCA에 연결하여

    초마다 상태값을 바꿔야하며

    외부에서도 끌수 있어합니다 (ex> 페이지 닫을때 타이머 종료)

    private enum CancelID { case timer }
    
    private func startTimer() -> Effect<Action> {
            return .run { send in
                for await time in timerStream() {
                    await send(.timer(time))
                }
            }.cancellable(id: CancelID.timer, cancelInFlight: true)
        }
    

    startTimer 함수는 Effect<Action> 을 방출합니다 Reducer 내부 코드이므로 Reducer의 Action d입니다

    send(Action)을 통해 시간을 전달 해주었습니다 이는 reduce 내부에서 상태를 바꿔줄겁니다

    또 cancellable을 지정해줬습니다 id 값이 필요한데 enum 으로 넣어줬습니다 (TCA 공식 예제 참고 함)

    //Reducer 
    
      case .requestDone: //타이머 시작 액션
          return startTimer()
      case let .timer(time):
          state.time = time
    

    리듀스 부분에서 startTimer 를 넣어줬고 타이머가 동작하게 됩니다

    그러면 초마다 .timer 액션이 돌게되고 상태값을 변경하여 View에서 저 상태를 보고 Ui 처리해주면 됩니다

    만약 페이지가 닫혀서 타이머를 끈다면

      case .close:
          return .cancel(id: CancelID.timer)
    
    

    이런식으로 cancel Effect를 리턴 해주면 됩니다.

    댓글

Designed by Tistory.