이모저모/Swift

Swift - Task.Sleep vs Timer

ARpple 2024. 4. 30. 16:51

 

Time: 특정 시간 간격이 경과한 후 지정된 메시지를 대상 개체에 전송하는 타이머입니다.

Timer: Single Thread 전용 클래스

  • 타이머는 실행 루프와 함께 작동
  • 실행 루프는 타이머에 대한 강력한 참조를 유지함으로, 실행 루프에 추가한 후에는 타이머가 강력한 참조를 유지하지 않아도 된다.
    ⇒ 스케쥴러에 등록만 하면 자동으로 실행한다는 의미인듯?
  • 타이머를 효과적으로 사용하려면 실행 루프의 작동 방식을 알고 있어야 합니다.
    ⇒ 자세한 내용은 스레딩 프로그래밍 가이드를 참조하세요.
  • 타이머는 실시간 메커니즘이 아니다.
  • 타이머의 실행 시간이 긴 실행 루프 콜아웃 중에 발생하거나 실행 루프가 타이머를 모니터링하지 않는 모드에 있는 경우, 타이머는 다음 실행 루프가 타이머를 확인할 때까지 실행되지 않습니다.
    따라서 타이머가 실제로 실행되는 시간은 상당히 늦어질 수 있습니다.
    타이머 허용 오차도 참조하세요.
  • 타이머는 코어 파운데이션에 대응하는 CFRunLoopTimer와 free bridging 브리징됩니다.
    자세한 내용은 무료 브리징을 참조하세요.

RunLoop

RunLoop의 생성과 실행

  • 비동기 처리를 위해 스레드 객체를 사용할 때, 자동으로 RunLoop가 생성되지만, 메인 스레드를 제외한 스레드에서는 RunLoop가 자동으로 실행되지는 않는다.
  • 즉, DispatchQueue를 통해 스레드 객체를 생성하면 RunLoop도 같이 생성되지만, 자동으로 실행되지는 않아서 개발자가 직접 실행시켜야 한다.
  • 그리고 RunLoop는 무한 반복적으로 실행되는 것이 아니라, 한 번의 루프가 도는 동안 수신받은 이벤트에 대한 핸들러를 수행한 후 대기상태로 돌아간다. 따라서 반복 실행이 필요한 경우 개발자가 반복문을 사용하여 RunLoop가 계속 실행되도록 구현해야 한다.

⇒ 메인 스레드의 RunLoop는 자동으로 반복 실행됨 > 따로 실행시켜줄 필요 없다.

Main RunLoop Mode와 동작 방식

 

RunLoop.Mode | Apple Developer Documentation

Modes that a run loop operates in.

developer.apple.com

 

 

iOS) 런 루프(RunLoop) 이해하기

안녕하세요 :) 소들입니다 오늘은 RunLoop라는 것에대해 공부를 해볼 건데여 음... 내용이 좀 어려울 수도 있어여!! 저도 오랜만에 다뤄서 완전히 이해하고 쓰는 내용이 아니라... (한 1년 전에 공부

babbab2.tistory.com

유저 Interation이 발생하면 RunLoop들은 non-default모드로 변경된다. 그러나 RunLoop.main은 오직 default모드에서만 실행(active)된다.

달리 말하면 RunLoop.main은 User interation이 끝날 때 Default Mode로 바뀐 후, 렌더링 처리 로직(Closure)을 실행한다.

GCD와의 차이점

RunLoom.main vs DispatchQueue.main
  • GCD의 가장 큰 특징은 비동기 Serialization ⇒ FIFO
  • 하나의 큐에서 작업을 돌리기 때문에 새로운 DispatchQueue.main 명시적으로 실행시 해당 작업을 우선적으로 돌림
  • RunLoom.main은 쓰레드에서 계속 반복되는 Loop에 직접 다음 작업을 넣는 방식
    사용자 상호작용 UI가 더 늦게 처리 될 수 있음

Timer 기본 사용 방법

스케쥴러에 등록

스케쥴러에 등록된 timer 스케쥴러에 없애기

Task.sleep vs Timer

좌측: Task.sleep(for:.seconds(1)) 으로 구현한 타이머

우측: Timer 인스턴스를 사용한 타이머 - 타이머 버튼을 누르는 시간 차이가 약간 존재...

TCA에서 AsyncStream Timer 사용하기

Timer Extension을 제작해 Stream으로 비동기 For 이터레이터 문 사용

extension Timer{
    static func eventAsyncStream() -> AsyncThrowingStream<(),Error> {
        return .init { continuation in
            let cancellable = Timer.publish(every: 1, on: .main, in:.common).autoconnect().sink(receiveValue: { _ in
                continuation.yield()
            })
            continuation.onTermination = {_ in
                cancellable.cancel()
            }
        }
    }
}

사용 코드

import Foundation
import ComposableArchitecture
@Reducer struct TimerFeature{
...
    var body: some ReducerOf<Self>{
        Reduce{ state, action in
            switch action{
                    ...
                    case .setTimerRunning(let count):
                        state.count = count
                return .run(priority: .high) { send in
                    for try await _ in Timer.eventAsyncStream(){
                        await send(.timerTick)
                    }
                }.cancellable(id: CancelID.timer)
            }
      }
}