본문 바로가기
iOS/개념

[RxSwift] Operator 연습 4탄 : Time Based Operator

by 나리._. 2021. 12. 14.

이번 포스팅에서는 시간의 흐름에 따라 데이터를 제어, 변동, 조작하는 오퍼레이터인  Time Based Operator를 정리할 것이다.

 

TimeBasedOperator는 buffer 연산자 계열이고

buffer 연산자 계열은 과거의 요소들을 subscriber에게 다시 재생하거나 잠시 버퍼를 두고 줄 수 있다.

 

1. replay

지나간 이벤트 방출에 대해 버퍼 사이즈 수만큼 새로운 subscriber에게 replay 해주는 연산자.

let intro = PublishSubject<String>()
let parrot = intro.replay(1) // buffer size=1
parrot.connect()

intro.onNext("1.hello")
intro.onNext("2.hi")

parrot
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)
    
intro.onNext("3.hi") // 구독 이후 발생 이벤트

인사말(1.hello, 2.hi) 이벤트가 방출되고 난 후 구독

2.hi는 출력되고 1.hello는 출력되지 않음.

buffer 사이즈를 1로 두었기 때문에 과거의 지나간 요소 중 가장 최신의 것 1개(여기서는 2.hi)만 받을 수 있다.

또, 구독 이후 발생 이벤트인 3.hi는 버퍼와 상관없이 무조건 받는다.

 

 

2. replayAll

개수 제한 없이 지나간 이벤트 방출에 대해 모두 출력해주는 연산자

let timeStone = doctorStrange.replayAll()
timeStone.connect()

doctorStrange.onNext("도르마무")
doctorStrange.onNext("거래를 하러왔다.")

timeStone
    .subscribe(onNext: {
        print($0) // 도르마무, 거래를 하러왔다. 모두 출력
    })
    .disposed(by: disposeBag)

 

 

3. buffer

buffer 연산자는 Observable이 발생하는 이벤트들을 지정한 시간 동안 (timeSpan) 지정한 개수만큼 (count) 들어온 이벤트를 배열로 묶어 방출해준다.

let source = PublishSubject<String>()

var count = 0
// 시간 흐름에 따라 특정 시간마다 함수 작동
let timer = DispatchSource.makeTimerSource()

// 현재시점부터 2초를 deadline으로 1초마다 반복
timer.schedule(deadline: .now()+2 , repeating: .seconds(1))

timer.setEventHandler {
    count += 1
    source.onNext("\(count)")
}
timer.resume()

source
    .buffer(
    timeSpan: .seconds(2), // 2초내에
    count: 2, // 최대 2개
    scheduler: MainScheduler.instance // 해당 연산자가 실행될 thread 설정
    )
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

여기서는 buffer를 이용하여 2초 동안 최대 2개를 받도록 설정해놓았기 때문에,

최대 2개까지만 제한을 두고 계속 array로 출력시키는 것을 볼 수 있다.

2초가 되기 전 2개가 받아졌다면 방출하고 다음 요소를 받는다.

또한 2초가 되었는데 아무것도 받아지지 않아도 빈 array를 방출한다.

 

 

4. window

buffer와 비슷하지만 buffer와 달리, array가 아닌 Observable로 방출하는 연산자이다.

let 만들어낼최대Observable수 = 1
let 만들시간 = RxTimeInterval.seconds(2)

let window = PublishSubject<String>()
var windowCount = 0
let windowTimerSource = DispatchSource.makeTimerSource()
windowTimerSource.schedule(deadline: .now()+2, repeating: .seconds(1))
windowTimerSource.setEventHandler {
    windowCount += 1
    window.onNext("\(windowCount)")
}
windowTimerSource.resume()

window
    .window(
        timeSpan: 만들시간,
        count: 만들어낼최대Observable수,
        scheduler: MainScheduler.instance
    ) // observable 방출
    .flatMap { windowObeservable -> Observable<(index: Int, element:String)> in
        return windowObeservable.enumerated()
    }
    .subscribe(onNext: {
        print("\($0.index)번째 옵저버블 요소 = \($0.element)")
    })
    .disposed(by: disposeBag)

여기서는 최대 옵저버블의 수를 1로 두었기 때문에 계속 0번째 옵저버블로 나온다.

 

 

5. delaySubscription

구독을 지연시키는 operator

let delaySource = PublishSubject<String>()
var delayCount = 0
let delayTimeSource = DispatchSource.makeTimerSource()
delayTimeSource.schedule(deadline: .now()+2, repeating: .seconds(1))
delayTimeSource.setEventHandler {
    delayCount += 1
    delaySource.onNext("\(delayCount)")
}
delayTimeSource.resume()

// 5초 지연 -> 5초가 지나고나서야 구독
delaySource
    .delaySubscription(.seconds(5), scheduler: MainScheduler.instance)
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

0~3도 이벤트가 방출되고 있지만 5초가 지난 시점에서부터 구독을 시작하기 때문에 4부터 출력한다.

 

 

6. delay

delaySubscription이 구독을 지연시켰다면, delay는 전체 시퀀스를 지연시키는 연산자이다.

구독은 즉시 하지만, element의 방출을 설정한 시간만큼 미루는 것이다.

let delaySubject = PublishSubject<Int>()

var delayCount = 0
let delayTimerSource = DispatchSource.makeTimerSource()
delayTimerSource.schedule(deadline: .now(), repeating: .seconds(1))
delayTimerSource.setEventHandler {
    delayCount += 1
    delaySubject.onNext(delayCount)
}
delayTimerSource.resume()

delaySubject
    .delay(.seconds(5), scheduler: MainScheduler.instance)
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

5초 뒤에 위와 같이 이벤트가 방출된다.

 

 

위의 연산자들에서 임의로 만들었던 timer를 rx로 바로 구현하는 방법인 interval과 timer를 정리해보자.

 

7. interval

설정한 간격(interval)을 두고 옵저버블을 생성

Observable<Int>
    // 3초 간격으로 0부터 int 출력
    .interval(.seconds(3), scheduler: MainScheduler.instance)
    .subscribe(onNext: {
        print($0) // 0, 1, 2, .. (3초마다 출력됨)
    })
    .disposed(by: disposeBag)

Int라고만 설정하고 생성자를 아무것도 사용하지 않았다.

그럼에도 불구하고 Int라는 타입만을 이용하여 interval 연산자가 타입 추론을 통해 0부터 3초 간격으로 방출하는 것을 볼 수 있다.

 

 

8. timer

interval과 유사하지만 두 가지 차이점이 있다.

1. 구독 이후 첫 번째 값 사이 간격 - due time 설정 가능

2. 반복 간격 설정 가능

Observable<Int>
    .timer(.seconds(5), period: .seconds(2), scheduler: MainScheduler.instance)
    .subscribe(onNext: {
        print($0) // 5초 뒤 2초 간격으로 0,1,2,3... 출력
    })
    .disposed(by: disposeBag)

여기서는 due time 5초 반복 간격 2초로 설정하였다.

 

번잡하게 count를 정하고 이벤트 핸들러를 만들고 실행하는 코드를 작성했던 임의의 timer 코드보다

interval과 timer를 이용했을 때 훨씬 더 직관적인 것을 볼 수 있다.

 

 

9. timeout

timer와 비슷하지만 timeout은 제한시간을 두는 연산자이다.

let 누르지않으면에러나는버튼 = UIButton(type: .system)
누르지않으면에러나는버튼.setTitle("누르세요", for: .normal)
누르지않으면에러나는버튼.sizeToFit()

PlaygroundPage.current.liveView = 누르지않으면에러나는버튼
누르지않으면에러나는버튼.rx.tap
    .do(onNext: {
        print("tap")
    })
    // 5초 안에 이벤트 발생하지 않으면 에러
    .timeout(.seconds(5), scheduler: MainScheduler.instance)
    .subscribe{
        print($0)
    }
    .disposed(by: disposeBag)

제한시간 (여기서 5초) 안에 이벤트 발생하지 않으면 아래와 같이 에러

눌렀을 때 이벤트 다음과 같이 방출

또 아무런 이벤트가 발생하지 않고 제한시간이 지나면 다음과 같이 에러가 뜬다.