이모저모/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)
}
}
}