[TCA] Effect #5 (Timers)
목차
- Timers 주제에 대해서 간단히 알아보기
- Timers와 관련한 예제
# Timers 주제에 대해서 간단히 알아보기
@Dependency(\.continuousClock) var clock
위의 디펜던시 예제처럼 사용할 수 있음 Swift Clocks 라이브러리에서 제공하는 Helper인 클럭에 .timer 메서드를 사용
비동기 코드로 시간을 처리하기 위한 `AsyncSequence` 친화적인 API를 제공
# Timers와 관련한 예제
// MARK: - Feature domain
struct Timers: Reducer {
struct State: Equatable {
var isTimerActive = false
var secondsElapsed = 0
}
enum Action {
case onDisappear
case timerTicked
case toggleTimerButtonTapped
}
@Dependency(\.continuousClock) var clock
private enum CancelID { case timer }
func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .onDisappear:
return .cancel(id: CancelID.timer)
case .timerTicked:
state.secondsElapsed += 1 // 애니메이션이 toggleTimerButtonTapped에서 적용 받아서 들어옴
return .none
case .toggleTimerButtonTapped:
state.isTimerActive.toggle()
return .run { [isTimerActive = state.isTimerActive] send in
guard isTimerActive else {
// isTimerActive 값이 false 이면 return
return
}
for await _ in self.clock.timer(interval: .seconds(1)) { // ✅ _AsyncTimerSequence<AnyClock<Duration>> 반환되고 라서 for _ 문으로 무한 반복
await send(.timerTicked, animation: .interpolatingSpring(stiffness: 3000, damping: 40)) // 애니메이션을 여기서 세팅해서 보내야 한다. 👍 이 애니메이션 좋다~!
}
}
.cancellable(id: CancelID.timer, cancelInFlight: true)
/*
cancelInFlight를 사용하는 부분이 특이한데, cancelInFlight는 동일한 효과가 있는지 판별
취소 가능한 effect를 effect로 취소 가능한 를 전환하려면 cancel(id:)에서 취소해야 하는 in-flight 효과를 위해 사용되는 식별자를 제공해야 한다.
문자열과 같이 해시 가능한 값은 식별자에 사용할 수 있지만, 식별자에 대한 새로운 유형을 정의하여 오타에 대한 약간의 보호 기능을 추가할 수 있습니다.
추측: 아직 완벽하게 이해한건 아니지만 enum 타입으로 식별자를 주는데 이게 String이 아니라서 약간의 보호 기능으로 언급한게 아닐까 싶다.
*/
}
}
}
// MARK: - Feature view
struct TimersView: View {
let store: StoreOf<Timers>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
Form {
AboutView(readMe: readMe)
ZStack {
Circle()
.fill(
AngularGradient(
gradient: Gradient(
colors: [
.blue.opacity(0.3),
.blue,
.blue,
.green,
.green,
.yellow,
.yellow,
.red,
.red,
.purple,
.purple,
.purple.opacity(0.3),
]
),
center: .center
)
)
.rotationEffect(.degrees(-90))
GeometryReader { proxy in
Path { path in
path.move(to: CGPoint(x: proxy.size.width / 2, y: proxy.size.height / 2))
path.addLine(to: CGPoint(x: proxy.size.width / 2, y: 0))
}
.stroke(.primary, lineWidth: 3)
.rotationEffect(.degrees(Double(viewStore.secondsElapsed) * 360 / 60))
}
}
.aspectRatio(1, contentMode: .fit)
.frame(maxWidth: 280)
.frame(maxWidth: .infinity)
.padding(.vertical, 16)
Button {
viewStore.send(.toggleTimerButtonTapped)
} label: {
Text(viewStore.isTimerActive ? "Stop" : "Start")
.padding(8)
}
.frame(maxWidth: .infinity)
.tint(viewStore.isTimerActive ? Color.red : .accentColor)
.buttonStyle(.borderedProminent)
}
.navigationTitle("Timers")
.onDisappear {
viewStore.send(.onDisappear)
}
}
}
}
// MARK: - SwiftUI previews
struct TimersView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
TimersView(
store: Store(initialState: Timers.State()) {
Timers()
}
)
}
}
}
타이머를 사용할 때는 사이드 이펙트에 주의해야겠다.
Timer가 interval로 도는건 for문을 통해 처리하던데 요 부분을 조금 더 면밀하게 살펴보면 좋겠다.
그리고 View쪽도 애니메이션이 정말 잘 구현되어 있는데. 요것도 잘 살펴보자.
그리고 무엇보다도 cancelInFlight 부분도 잘 살펴보자!!
'apple > TCA' 카테고리의 다른 글
[TCA] Navigation (화면전환 총 정리) (0) | 2023.10.09 |
---|---|
[TCA] Effect #6 (WebSocket) (0) | 2023.10.08 |
[TCA] Effect #4 (Refreshable) (0) | 2023.10.07 |
[TCA] Effect #3 (LongLiving) (0) | 2023.10.07 |
[TCA] Effect #2 (Cancellation) (0) | 2023.10.07 |