interval
특정 주기마다 정수를 배출하는 옵저버블이 필요하면 이 연산자를 사용한다.
public static func interval(_ period: RxTimeInterval, scheduler: SchedulerType)
-> Observable<Element> {
return Timer(
dueTime: period,
period: period,
scheduler: scheduler
)
}
interval연산자는 타입메서드로 구현되어있다.
첫 번째 파라미터로 반복 주기를 받는데, RxTimeInterval은 DispatchTimeInterval과 같다.
두번째 파라미터는 정수를 방출할 스케쥴러를 지정한다.
연산자가 return하는 옵저버블은 지정된 주기마다 정수를 반복적으로 방출한다.
종료시점을 지정하지 않기 때문에 직접 dispose를 지정하기 전까지 계속 방출한다.
방출하는 정수의 형식은 Int로 제한되지 않는다. FixedWidthInteger형식으로 지정되어 있기 떄문에 Int를 포함한 다른 정수형식을 모두 사용할 수 있다.
반대로 Double이나 String같은 형식을 사용할 수 없다.
let i = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
이는 1초마다 Int형식의 정수를 방출하는 옵저버블이 생성된다.
let i = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
let subscription = i.subscribe{ print("1 >> \($0)")}
/*
1 >> next(0)
1 >> next(1)
1 >> next(2)
1 >> next(3)
1 >> next(4)
1 >> next(5)
1 >> next(6)
1 >> next(7)
...
*/
종료시점을 지정하지 않기 때문에 무한정 반복된다.
그래서 동작을 중지하고 싶다면 dispose메서드를 직접 호출해야한다.
5초뒤에 dispose메서드를 호출한다.
let i = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
let subscription = i.subscribe{ print("1 >> \($0)")}
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: {
subscription.dispose()
})
//1 >> next(0)
//1 >> next(1)
//1 >> next(2)
//1 >> next(3)
//1 >> next(4)
Interval 연산자가 생성하는 옵저버블은 내부에 타이머를 가지고 있다.
이 타이머가 시작되는 시점은 생성 시점이 아니다. 구독자가 구독을 시작하는 시점이다.
새로운 구독자가 생길 때 마다 새로운 타이머가 시작된다.
interval 연산자의 핵심은 새로운 구독이 추가되는 시점에 내부의 타이머가 시작된다는 것이다.
timer
interval과 마찬가지로 정수를 반복적으로 방출하는 옵저버블을 생성한다.
하지만 지연시간과 반복주기를 모두 지정할 수 있고 두 값에 따라서 동작방식이 달라진다.
public static func timer(_ dueTime: RxTimeInterval, period: RxTimeInterval? = nil, scheduler: SchedulerType)
-> Observable<Element> {
return Timer(
dueTime: dueTime,
period: period,
scheduler: scheduler
)
}
timer연산자는 타입메서드로 구현되어있다. 그리고 리턴되는 옵저버블이 방출하는 요소는 FixedWidthInterger형식으로 제한되어있다.
여기에서 파라미터를 보면 3개가 선언되어있다.
첫 번째 파라미터는 첫 번째 요소가 방출되는 시점까지의 상대적인 시간이다. 구독을 시작하고, 첫 번쨰 요소가 구독자에게 전달되는데 걸리는 상대적인 시간이다. 여기에 1초를 전달하면 구독 후 1초뒤에 요소가 전달된다.
두 번째 파라미터는 반복주기이다. 반복주기는 기본값이 nil로 선언되어있다. 이 값에 따라 타이머 연산자의 동작방식이 달라진다.
생략되어 있는경우 하나의 요소만 방출하고 종료된다.
세 번째 파라미터에는 타이머가 동작할 스케쥴러를 전달한다.
Observable<Int>.timer(.seconds(1), scheduler: MainScheduler.instance)
.subscribe{ print($0)}
.disposed(by: bag)
//next(0)
//completed
1초뒤에 첫 번째 요소가 전달되고 바로 CE가 전달된다.
Observable<Int>.timer(.seconds(1), period: .milliseconds(500), scheduler: MainScheduler.instance)
.subscribe{ print($0)}
.disposed(by: bag)
/*
next(0)
next(1)
next(2)
next(3)
next(4)
next(5)
next(6)
next(7)
next(8)
next(9)
...
*/
반복주기를 0.5초로 설정하면
0.5초마다 1씩 증가하는 정수가방출된다. Interval처럼 타이머를 중지하고 싶다면 직접 dispose해야한다.
timeout
public func timeout(_ dueTime: RxTimeInterval, scheduler: SchedulerType)
-> Observable<Element> {
return Timeout(source: self.asObservable(), dueTime: dueTime, other: Observable.error(RxError.timeout), scheduler: scheduler)
}
timeout연산자는 소스 옵저버블이 방출하는 모든 요소에 timeout정책을 적용한다.
첫 번째 파라미터로 timeout시간을 전달하는데 이 시간안에 NE를 방출하지 않으면 EE를 전달하고 종료한다.
에러형식은 RxError.timeout이다.
반대로 타임아웃시간 전에 새로운 이벤트를 방출하면 구독자에게 그대로 전달한다.
public func timeout<Source: ObservableConvertibleType>(_ dueTime: RxTimeInterval, other: Source, scheduler: SchedulerType)
-> Observable<Element> where Element == Source.Element {
return Timeout(source: self.asObservable(), dueTime: dueTime, other: other.asObservable(), scheduler: scheduler)
}
3개의 파라미터를 받는 timeout연산자
두번째 파라미터로 옵저버블을 전달하는것을 제외하면 나머진 동일하다.
여기에선 타임아웃이 발생하면 에러이벤트를 전달하는것이 아니라 구독대상을 두번쨰 파라미터로 전달된 옵저버블로 교체한다.
3초이내에 새로운 NE가 전달되지 않으면 EE를 전달하고 종료하는 코드
subject.timeout(.seconds(3), scheduler: MainScheduler.instance)
.subscribe{print($0)}
.disposed(by: bag)
timer를 이용해 subject에 1초마다 한번씩 NE를 보내본다.
subject.timeout(.seconds(3), scheduler: MainScheduler.instance)
.subscribe{print($0)}
.disposed(by: bag)
Observable<Int>.timer(.seconds(1), period: .seconds(1), scheduler: MainScheduler.instance)
.subscribe(onNext: { subject.onNext($0)})
.disposed(by: bag)
/*
next(0)
next(1)
next(2)
next(3)
next(4)
next(5)
next(6)
next(7)
next(8)
*/
1초마다 NE가 방출되기 때문에 종료되지 않는다.
반면 타임아웃 시간이 3초인 서브젝트에 2초마다 전달하는데 주기를 5초로 한다면?
subject.timeout(.seconds(3), scheduler: MainScheduler.instance)
.subscribe{print($0)}
.disposed(by: bag)
Observable<Int>.timer(.seconds(2), period: .seconds(5), scheduler: MainScheduler.instance)
.subscribe(onNext: { subject.onNext($0)})
.disposed(by: bag)
//next(0)
//error(Sequence timeout.)
3초마다 새로운 NE가 방출되지 않으면 타임아웃이 되는 서브젝트에 2초마다 NE를 보내는데 5초마다 보낸다
첫 0은 2초만에 들어오지만 다음 요소는 5초뒤 + 2초뒤인 7초뒤에 들어오게 되므로 3초가 된순간 Error메세지가 방출된다.
만약 타임아웃이 발생되는 시점에 에러이벤트가 아닌 0을 방출하고 싶다면?
subject.timeout(.seconds(3), other: Observable.just(0) ,scheduler: MainScheduler.instance)
.subscribe{print($0)}
.disposed(by: bag)
Observable<Int>.timer(.seconds(2), period: .seconds(5), scheduler: MainScheduler.instance)
.subscribe(onNext: { subject.onNext($0)})
.disposed(by: bag)
//next(0)
//next(0)
//completed
첫 번째 이벤트는 서브젝트가 전달한 이벤트이다. 그 후에는 5초뒤에 전달되기때문에 타임아웃이 발생한다.
타입아웃이 발생하면 두번째 파라미터로 전달된 옵저버블을 구독한다.
그리고 여기에서 전달하는 이벤트가 구독자에게 전달된다.
이어서 completed가 전달되고 구독이 종료된다.
delay
delay연산자는 NE가 구독자로 전달되는 시점을 지정한 시간만큼 지연시킨다.
첫 번째 파라미터에는 지연시킬 시간을 전달하고 두 번째 파라미터에는 딜레이타이머를 실행할 스케쥴러를 전달한다.
연산자가 리턴하는 옵저버블은 원본옵저버블과 동일한 형식을 가지고 있지만 NE가 구독자에게 전달되는 시점이 첫 번째 파라미터에 전달한 시간만큼 지연된다.
에러이벤트는 지연없이 즉시 전달된다.
func currentTimeString() -> String {
let f = DateFormatter()
f.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
return f.string(from: Date())
}
Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
.take(10)
.debug()
.delay(.seconds(5), scheduler: MainScheduler.instance)
.subscribe{ print(currentTimeString(), $0 )}
.disposed(by: bag)
/*
2023-02-20 17:25:32.555: delay.playground:41 (__lldb_expr_500) -> subscribed
2023-02-20 17:25:33.570: delay.playground:41 (__lldb_expr_500) -> Event next(0)
2023-02-20 17:25:34.568: delay.playground:41 (__lldb_expr_500) -> Event next(1)
2023-02-20 17:25:35.568: delay.playground:41 (__lldb_expr_500) -> Event next(2)
2023-02-20 17:25:36.568: delay.playground:41 (__lldb_expr_500) -> Event next(3)
2023-02-20 17:25:37.568: delay.playground:41 (__lldb_expr_500) -> Event next(4)
2023-02-20 17:25:38.568: delay.playground:41 (__lldb_expr_500) -> Event next(5)
2023-02-20 17:25:38.572 next(0)
2023-02-20 17:25:39.568: delay.playground:41 (__lldb_expr_500) -> Event next(6)
2023-02-20 17:25:39.573 next(1)
2023-02-20 17:25:40.567: delay.playground:41 (__lldb_expr_500) -> Event next(7)
2023-02-20 17:25:40.575 next(2)
2023-02-20 17:25:41.568: delay.playground:41 (__lldb_expr_500) -> Event next(8)
2023-02-20 17:25:41.577 next(3)
2023-02-20 17:25:42.568: delay.playground:41 (__lldb_expr_500) -> Event next(9)
2023-02-20 17:25:42.568: delay.playground:41 (__lldb_expr_500) -> Event completed
2023-02-20 17:25:42.568: delay.playground:41 (__lldb_expr_500) -> isDisposed
2023-02-20 17:25:42.579 next(4)
2023-02-20 17:25:43.580 next(5)
2023-02-20 17:25:44.582 next(6)
2023-02-20 17:25:45.584 next(7)
2023-02-20 17:25:46.585 next(8)
2023-02-20 17:25:47.587 next(9)
2023-02-20 17:25:47.588 completed
*/
delay연산자는 구독시점을 연기하진 않는다.
구독자가 추가되면 바로 시퀀스가 추가된다.
.debug()가 출력한 로그를 보면 1초마다 계속 NE를 방출하고 있다. 바로 이어서 구독자에서 추가한 로그는 출력되지 않는다.
구독자에서 추가한 로그는 5초뒤에 출력된다. 원본 옵저버블이 방출한 NE가 5초뒤에 구독자에게 전달된것.
next(0) 원본옵저버블이 요소를 방출한 시간은 25분 33초, 구독자로 전달된 시간은 25분 38초이다.
delay연산자는 이렇게 NE가 방출된 다음에 구독자로 전달되는 시점을 지연시킨다.
만약 구독 시점을 지연시키고 싶다면 delaysubscription연산자를 사용한다.
delaySubscription
Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
.take(5)
.debug()
.delaySubscription(.seconds(7), scheduler: MainScheduler.instance)
.subscribe{ print(currentTimeString(), $0)}
.disposed(by: bag
/*
...7초뒤
2023-02-20 17:35:03.895: delaySubscription.playground:41 (__lldb_expr_502) -> subscribed
2023-02-20 17:35:04.899: delaySubscription.playground:41 (__lldb_expr_502) -> Event next(0)
2023-02-20 17:35:04.899 next(0)
2023-02-20 17:35:05.898: delaySubscription.playground:41 (__lldb_expr_502) -> Event next(1)
2023-02-20 17:35:05.898 next(1)
2023-02-20 17:35:06.899: delaySubscription.playground:41 (__lldb_expr_502) -> Event next(2)
2023-02-20 17:35:06.899 next(2)
2023-02-20 17:35:07.899: delaySubscription.playground:41 (__lldb_expr_502) -> Event next(3)
2023-02-20 17:35:07.899 next(3)
2023-02-20 17:35:08.899: delaySubscription.playground:41 (__lldb_expr_502) -> Event next(4)
2023-02-20 17:35:08.899 next(4)
2023-02-20 17:35:08.900: delaySubscription.playground:41 (__lldb_expr_502) -> Event completed
2023-02-20 17:35:08.900 completed
2023-02-20 17:35:08.901: delaySubscription.playground:41 (__lldb_expr_502) -> isDisposed
*/
실행하면 7초뒤에 1초간격으로 NE가 전달되며 구독자에게 바로 전달된다.
구독시점을 지연시킬 뿐 NE가 전달되는 시점은 지연시키지 않는다.