combineLatest, merge, switch, zip,amb
payWard
* 이 포스트는 RxSwift 4.3.1, swift 4.2 버전을 기준으로 작성되었습니다.
Observable 의 결합은 연결고리가 있는 몇가지 이벤트들을 같이 처리 해야 할때 사용할 수 있다.
RxSwift, 시작하기 에서 언급한 것처럼 url request 두개의 응답을 받아서 처리하고 싶을때나,
아이디와 비밀번호 입력을 받고, 각각 이벤트들을 섞어서 유효성을 검사하고 싶을때를 예로 들수 있다.
합성과 변형의 경우 가장 빈번하고 유용하게 사용되는 것들이다.
이번 포스트에서는 RxSwift 에서 지원하는 4가지 종류의 Observable 결합 함수들에 대해서 알아보겠다.
combineLatest 는 두 Observable 의 각각의 이벤트가 발생할때 두 Observable의 마지막 이벤트들을 묶어서 전달한다. 두 이벤트의 타입은 달라도 된다.
http://rxmarbles.com/#combineLatest
예제
let boys = Observable.from(["tom","jhon","jack"])
let girls = Observable.from(["annie","amanda","jane"])
Observable.combineLatest(boys, girls, resultSelector: { (boy:String,girl:String) in
return (boy,girl)
}).subscribe{ event in
print(event)
}.disposed(by: disposeBag)
설명
한쌍의 남여만이 춤을 추는 무대가 있다.
남여가 한쌍이 되면 춤을 추고, 다음 남자 혹은 여자가 오면 이전의 남자 혹은 여자가 교체된다.
아래의 결과처럼 이벤트가 발생했다.
결과
next(("tom", "annie"))
next(("jhon", "annie"))
next(("jhon", "amanda"))
next(("jack", "amanda"))
next(("jack", "jane"))
* RxSwift 4.3.1 버전 기준으로 combineLatest로 최대 합성 가능한 Observable 의 갯수는 8개이다.
두 개의 Observable 을 합성하지만, 한쪽 Observable 의 이벤트가 발생할때에 합성해주는 메서드이다.
합성할 다른쪽 이벤트가 없다면 이벤트는 스킵된다
http://rxmarbles.com/#withLatestFrom
예제
// 직원은 4명이며, 중간에 잭은 3타임을 일한다. 파트타임 시간 간격은 2 ( 마지막 직원은 영업시간이 끝나서 1시간 일한다 )
let employee = Observable<Int>.interval(2, scheduler: MainScheduler.instance).take(6).map{ ["tom","jhon","jack","","","park"][$0] }.filter{ !$0.isEmpty }
// 임금은 1시간 단위로 10달러를 지급하며, 현재 출근해 있는 직원에게 지급한다.
let payWard = Observable<Int>.interval(1, scheduler: MainScheduler.instance).take(12).map{ ("\($0) o'clock","$10") }
payWard.withLatestFrom(employee, resultSelector:{ (pay:(String,String),name:String) in
return (pay.0,pay.1,name)
}).subscribe(onNext: { event in
print(event)
}).disposed(by: disposeBag)
설명
매 시간마다 출근한 직원에게 10 달러의 임금을 지불한다.
지급 시간에 대한 이벤트를 기준으로 withLatestFrom로 현재 근무자 이벤트를 합성하면 원하는 임금 지불 이벤트를 만들 수 있다. 상황 설정이 이 메서드의 가치를 완전히 표현하지는 못했다.
스킵되어도 되는 이벤트를 가진 Observable 과 이벤트 처리의 중심이 되는 Observable 가 필요할때
사용하면 유리하다.
결과
("1 o\'clock", "$10", "tom")
("2 o\'clock", "$10", "tom")
("3 o\'clock", "$10", "jhon")
("4 o\'clock", "$10", "jhon")
("5 o\'clock", "$10", "jack")
("6 o\'clock", "$10", "jack")
("7 o\'clock", "$10", "jack")
("8 o\'clock", "$10", "jack")
("9 o\'clock", "$10", "jack")
("10 o\'clock", "$10", "jack")
("11 o\'clock", "$10", "park")
* 스킵되는 이벤트를 만들고 싶다면, 타이밍을 두고 이벤트를 발생하는 Observable 을 만들면 된다.
여기서는 empty string 으로 이벤트를 필터링 해서 딜레이 되는 구간을 만들었다.
같은 타입의 이벤트를 발생하는 Observable 을 합성하는 함수이며, 각각의 이벤트를 모두 수신할 수 있다.
예제.
let redTeam = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.map { "red : \($0)" }
let blueTeam = Observable<Int>.interval(2, scheduler: MainScheduler.instance)
.map { "blue : \($0)" }
let startTime = Date().timeIntervalSince1970
Observable.of(redTeam,blueTeam).merge().subscribe{ event in
print("\(event) : \(Int(Date().timeIntervalSince1970 - startTime))")
}.disposed(by: disposeBag)
설명
블루팀과 레드팀의 선수들이 계주를 한다.
레드팀의 선수들은 1초에 한번 트랙을 돌고, 블루팀은 2초에 한번 돈다.
각팀의 선수가 출발하는 모든 시간을 기록해두고 싶다고 가정하면 모든 이벤트의 시간을 측정하면 된다.
merge 를 사용하면 두 Observable 모든 이벤트가 발생할때 처리가 가능하다.
결과
next(red : 0) : 1
next(red : 1) : 2
next(blue : 0) : 2
next(red : 2) : 3
next(red : 3) : 4
next(blue : 1) : 4
next(red : 4) : 5
이벤트 타입이 같지 않으면 merge할 수 없다.
( 이럴땐 Observable의 변형을 통해 이벤트를 가공해서 타입을 맞추면 된다. )
observale 을 switch 할 수 있는 observable 이다.
이벤트를 수신하고 싶은 observable로 바꾸면 해당 이벤트가 발생하는 것을 수신할 수 있다.
예제
let aSubject = PublishSubject<String>()
let bSubject = PublishSubject<String>()
let switchTest = BehaviorSubject<Observable<String>>(value :aSubject)
switchTest.switchLatest().subscribe{ event in
print(event)
}.disposed(by: disposeBag)
aSubject.on(.next("AA-1"))
switchTest.on(.next(bSubject))
aSubject.on(.next("AA-2"))
bSubject.on(.next("BB-1"))
bSubject.on(.next("BB-2"))
결과
next(AA-1)
next(BB-1)
next(BB-2)
AA-2 이벤트는 수신하지 않은 것을 확인 할 수 있다.
* 여러 Observable 중 선택적으로 처리하고 싶을 때 사용하면 된다.
zip 은 두 Observable 의 발생 순서가 같은 이벤트를 조합해서 이벤트를 발생한다.
이벤트가 조합 되지 않으면 이벤트가 발생하지 않는 것에 주목하자.
예제
let boys = Observable.from(["tom","jhon","jack"])
let girls = Observable.from(["annie","amanda","jane"])
Observable.zip(boys, girls){ ($0,$1) }.subscribe{ event in
print(event)
}.disposed(by: disposeBag)
설명
결혼식장이다. 신랑과 신부가 함께 식장으로 들어가는 이벤트를 만들었다.
각 대기실에서 신랑과 신부가 순서대로 나오면 합쳐서 입장한다.
항상 순서가 동일한 두개의 이벤트를 합쳐야 할때 사용하면 유리하다.
결과
next(("tom", "annie"))
next(("jhon", "amanda"))
next(("jack", "jane"))
concat 은 두개 ,혹은 그 이상의 Observable 을 직렬로 연결한다는 개념으로 이해하자.
하나의 Observable 이 완료될때까지 그 이벤트들을 전달하고 완료가 되면 그 다음 Observable 의 이벤트를 연이어 전달한다. 두번째 Observable 이 complete 되면 종료된다.
예제
let gold = Observable.from(["tom","jhon","jack"])
let normal = Observable.from(["annie","amanda","jane"])
gold.concat(normal).subscribe{ event in
print(event)
}.disposed(by: disposeBag)
설명
콘서트 장에서 골드좌석 티켓을 가진 사람들이 우선입장하고 일반석 티켓은 이다음에 입장한다.
어느 Observable이 완료된뒤에 그 다음 Observable 이 수행되어야 할때 사용하면 유리하다.
결과
next(tom)
next(jhon)
next(jack)
next(annie)
next(amanda)
next(jane)
맨 처음 발생한 옵저버블의 이벤트만 사용한다.
1:1 대전 게임시작하기를 눌럿고, 본인 이외의 3명이 마찬가지로 게임시작하기를 눌렀다고 가정하자.
게임클라이언트가 게임 시작하기를 누른 사람들 모두를 연결을 시도하고 먼저 연결된 사람들이 게임을 시작한다. 게임방은 아쉽게도 하나밖에 존재할수 없다고 하자.
예제
let playerA = Observable<Int>.interval(5, scheduler: MainScheduler.instance).map{ "matched player A: \($0)" }
let playerB = Observable<Int>.interval(2, scheduler: MainScheduler.instance).map{ "matched player B: \($0)" }
let playerC = Observable<Int>.interval(3, scheduler: MainScheduler.instance).map{ "matched player C: \($0)" }
playerA.amb(playerB).amb(playerC).subscribe(onNext: { e in
print(e)
}).disposed(by: disposeBag)
설명
플레이어 A는 5초 , B는 2초, C는 3초 뒤에 이벤트가 발생한다.
B가 가장먼저 이벤트가 발생했으므로 B의 이벤트만 받는다. 다른 A,C의 이벤트는 전달되지 않는다.
결과
matched player B: 0
matched player B: 1
...
처음 이벤트 값을 넣어줄수 있다.
함수 원형
func startWith(_ elements: E ...)
-> Observable<E>
예제
let peoples = Observable.from(["spider man","iron man"])
peoples.startWith("super hero").debug().subscribe().disposed(by: disposeBag)
결과
(startWithTest()) -> subscribed
(startWithTest()) -> Event next(super hero)
(startWithTest()) -> Event next(spider man)
(startWithTest()) -> Event next(iron man)
(startWithTest()) -> Event completed
(startWithTest()) -> isDisposed
이처럼 여러 형태의 합성을 통해 원하는 이벤트를 조합할 수 있다.
개인적인 의견으로 Rx의 가장 매력적인 기능 중 하나라고 생각한다.