Apple 공식문서에서 RunLoop 클래스는 thread safe 하지 않아, 현재 thread에서만 RunLoop 메서드를 호출해야 한다고 합니다.
아래는 RunLoop를 활용해 1초마다 print를 하는 timer를 만든 예제 입니다.
import Foundation
let runLoop = RunLoop.main
let subscription = runLoop.schedule(
after: runLoop.now,
interval: .seconds(1),
tolerance: .milliseconds(100)) {
print("Timer fired")
}
위 코드는 1초마다 "Timer fired" 가 프린트 되며, 아래처럼 Cancellable을 이용해 타이머를 취소시킬 수도 있습니다.
import Foundation
let runLoop = RunLoop.main
let subscription = runLoop.schedule(
after: runLoop.now,
interval: .seconds(1),
tolerance: .milliseconds(100)) {
print("Timer fired")
}
// 추가
runLoop.schedule(after: .init(Date(timeIntervalSinceNow: 3.0))) {
subscription.cancel()
}
Timer
timer publisher는 아래와 같이 만들 수 있습니다.
let main = Timer.publish(every: 1.0, on: .main, in: .common)
두 가지 매개변수의(on, in) 역할
On:어떤 RunLoop에서 타이머를 사용할지, (위 예제에선 main thread) In:어떤 방식으로 타이머 Runloop를 실행할 건지, (여기선 기본값 common)
NOTE Runloop의 동작방식을 모른다면 그냥 기본값을 사용하길 권장 합니다.
아래처럼 'on 파라미터'에 current를 사용해, 모든 thread에 대해 RunLoop를 얻을 수 있다는데.. 안정성을 위해선 RunLoop.main을 사용하라는 말이 있음..
let current = Timer.publish(every: 1.0, on: .current, in: .common)
필수!
타이머가 반환하는 Publisher는 ConnectablePublisher 인데,
이 Publisher를 구독하여 실행하려면 connect() 메서드를 호출해야합니다.
첫 subscriber가 구독할때, 자동으로 연결시켜주는 자매품 autoconnect() 메서드도 있어요!
따라서, 구독 시 타이머를 시작할 publisher를 만드는 방법은 아래처럼 작성하면 됩니다.
let publisher = Timer
.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
아래 Timer는 현재 날짜를 반복적으로 방출하고, Publisher.Output 타입은 Date 입니다. scan 연산자를 사용해 증가값을 내보내는 타이머를 만듭니다.
let subscription = Timer
.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
// 이전 값에 +1을 함 (reduce는 최종값만 나타내지만, scan은 지속적으로 값을 보여줌)
.scan(0) { counter, _ in
counter + 1
}
.sink { counter in
print("Counter is \(counter)")
}
// Counter is 1
// Counter is 2
// Counter is 3
// Counter is 4
// .....
DispatchQueue
DispatchQueue를 사용하여 타이머를 생성할 수 있습니다.
아래는 DispatchQueue를 이용해 1초마다 이벤트를 방출하는 코드입니다.
import Foundation
import Combine
let queue = DispatchQueue.main
// 1
let source = PassthroughSubject<Int, Never>()
// 2
var counter = 0
// 3
let cancellable = queue.schedule(
after: queue.now,
interval: .seconds(1)
) {
source.send(counter)
counter += 1
}
// 4
let subscription = source.sink {
print("Timer emitted \($0)")
}
1. 타이머 값을 보낼 Subject를 만듭니다. 2. 타이머가 실행 될때마다 카운터를 증가시킵니다. 3. 1초마다 queue에 action을 (counter + 1, source에 counter 등록) 저장해놓습니다. 4. 타이머 값을 얻기 위해 subject를 구독합니다.