combine operator 즉 결합 연산자는 관찰 가능한 시퀀스를 다양한 방법으로 결합하는 목적으로 사용합니다.
이미지 에서 볼수 있듯이 2개의 Observable이 merge 를 통하여 하나의 Observable로 병합 되었습니다.
이는 Thread 로 나누어 병합 하는게 아닌 ( A Thread 와 B Thread 를 순서대로 병합한다)
Emit 되는 Event 들을 먼저 Merge 되는 순서대로 병합하여 하나의 Observable 로 만듭니다.
이와 비슷한 operator를 take , skip 에서 볼수 있었는데 그땐 병합이 아닌 조건을 통한 무시,차단이라고 볼수 있었습니다.
let alpha = PublishSubject<String>()
let beta = PublishSubject<String>()
let combine = Observable.of(alpha,beta)
combine
.merge()
.subscribe(onNext: {
print($0)
}).disposed(by: disposeBag)
alpha.onNext("A")
beta.onNext("가")
alpha.onNext("B")
beta.onNext("나")
alpha.onNext("C")
beta.onNext("다")
/*
A
가
B
나
C
다
*/
이처럼 onNext 된 순서에 맞게 ouput은 병합이 되어서 나오게 됩니다.
PublishSubject 는 Subscribe 가 이루어진 이후 발생하는 Event 를 Emit 합니다.
merge() 는 subject 의 Event 를 onNext 되는 순서대로 병합합니다.
또한
당연한 이야기 이지만 데이터 타입이 맞지 않으면 실행 되지 않습니다.
하지만 프로그래밍을 하게 되면 각기 다른 타입의 데이터들을 병합하여 사용해야 하는 상황이 생길수 있습니다
그럴땐 데이터 타입의 변경을 통해 수행할수 있습니다.
let str = Observable.of("string")
let num = Observable.of(1)
let doub = Observable.of(1.2345)
_ = Observable.merge(str.map { $0 as AnyObject },num.map { $0 as AnyObject },doub.map { $0 as AnyObject })
.subscribe(onNext : {
print("AnyObject = \($0)")
}).disposed(by: disposeBag)
_ = Observable.merge(str.map { "\($0)" },num.map { "\($0)" },doub.map { "\($0)" })
.toArray()
.subscribe { single in
print(single)
}.disposed(by: disposeBag)
AnyObject = string
AnyObject = 1
AnyObject = 1.2345
success([string, 1, 1.2345])
AnyObject 외의 다른 Type 으로 Casting 불가능
let strSub = PublishSubject<String>()
let strOb = strSub.asObservable().map { $0 as AnyObject }
let numSub = PublishSubject<Int>()
let numOb = numSub.asObservable().map { $0 as AnyObject }
Observable.of(strOb, numOb).merge()
.subscribe(onNext : {
print($0)
}).disposed(by: disposeBag)
strSub.onNext("a")
strSub.onNext("b")
numSub.onNext(1)
numSub.onNext(2)
strSub.onNext("c")
a
b
1
2
c
Concat 연산자 는 여러 Observable의 출력을 연결하여 단일 Observable처럼 작동합니다
첫 번째 Observable에서 내보낸 모든 항목은 두 번째 Observable에서 내보낸 항목보다 먼저 내보내집니다
let front = Observable.of(1,2,3)
let back = Observable.of(4,5,6)
front
.concat(back)
.subscribe(onNext : {
print($0)
}).disposed(by: disposeBag)
front
.concat(back) // 1 , 2 , 3 , 4 , 5 , 6
.element(at: 5) // 6
.filter { $0 > 3 } // 조건 true
.toArray() // array 생성 : [ 6 ]
.subscribe { single in
print(single)
}.disposed(by: disposeBag)
/*
1
2
3
4
5
6
success([6])
*/
concatMap 은 flatMap 과 흡사 합니다.
하지만 flatMap 은 순서가 보장되지 않는다
concatMap 은 순서가 보장 된다
let section = [ "Alphabet" : Observable.of("A","B","C"),
"Number" : Observable.of("1","2","3")
]
let observable = Observable.of("Alphabet","Number")
observable.concatMap {
section[$0] ?? .empty()
}.subscribe(onNext: {
print($0)
}).disposed(by: disposeBag)
//print
A
B
C
1
2
3
이미지 출처: 링크텍스트
이 처럼 concat 과 반대로 앞에 추가 하는 startWith 연산자도 존재합니다.
let alphaObservable = Observable.of("A","B","C")
alphaObservable
.startWith("Alphabet")
.subscribe(onNext: {
print($0)
}).disposed(by: disposeBag)
//print
Alphabet
A
B
C
항목이 두 Observable 중 하나에 의해 방출되면 지정된 함수를 통해 각 Observable에서 방출된 최신 항목을 결합하고 이 함수의 결과에 따라 항목을 방출합니다.
사진에서 볼수 있듯이 두개의 Observable 하나에서 방출(Emit)이 이루어 지면
combineLastest 는 두개의 Observable의 최신 값 들을 가져옵니다.
그이후 시퀀스를 결합 클로저를 통해서 전달되게 됩니다.
말로만 이해하긴 조금 어려워 보이니 코드를 통해서 살펴보겠습니다.
let number = Observable.of("1","2","3")
let alphabet = Observable.of("A","B","C")
Observable
.combineLatest(number, alphabet) { alpha , num in
"\(alpha) === \(num)"
}.subscribe(onNext: {
print($0)
}).disposed(by: disposeBag)
//print
1 === A
2 === A
2 === B
3 === B
3 === C
combineLatest 는 두개 의 Observable 을 감시 하며 최신 값을 추적하여 결합 시킵니다.
이 두개의 그림을 보게 되면 A가 2보다 뒤에 있기에 1 자체 가 무시되는걸 볼수 있습니다.
number에 첫번째 방출된 1은 무시되고 alphabet에서 A가 방출되어 number 에 최신 값을 추적합니다.
추적 한 값은 1 입니다. 이유는 두개의 Observable 이 각각의 이벤트를 방출하여 결하기 때문에.
A방출된 가장 최신값인 1을 추적 하게 됩니다.- 1A
다음 number 에 2가 방출됩니다.
여기서 alphabet 의 최신 값은 A 이기 때문에 2A 가 됩니다.
func testCombineLatest() {
let l = PublishSubject<String>()
let r = PublishSubject<String>()
_ = Observable.combineLatest(l, r,resultSelector: { (lastL , lastR ) in
"\(lastL)\(lastR)"
}).subscribe(onNext : {
print($0)
}).disposed(by: disposeBag)
print("-> Left")
l.onNext("이\t")
print("-> Right")
r.onNext("진성")
print("-> Left2")
l.onNext("이이\t")
print("-> Right2")
r.onNext("진진성성")
}
-> Left
-> Right
이 진성
-> Left2
이이 진성
-> Right2
이이 진진성성
let choice : Observable<DateFormatter.Style> = Observable.of(.full, .medium)
let dates = Observable.of(Date())
let observable = Observable.combineLatest(choice, dates) { (format, when) -> String in
let formatter = DateFormatter()
formatter.dateStyle = format
return formatter.string(from: when)
}
observable
.subscribe(onNext: { value in
print(value)
}).disposed(by: disposeBag)
2022년 3월 14일 월요일
3/14/22
위 예제에서 살펴볼수 있는 것은
combineLatest 는 데이터 타입이 달라도 실행 가능하다는 것입니다.
또한 collection Type 으로도 실행 가능하다.
let a = PublishSubject<String>()
let b = PublishSubject<String>()
_ = Observable.combineLatest([a,b]) {
$0.joined(separator: " ")
}
하지만 컬렉션 타입에 경우 시퀀스의 Type이 동일해야 실행 가능하다.
지정된 함수를 통해 여러 Observable의 방출을 결합하고 이 함수의 결과를 기반으로 각 조합에 대해 단일 항목을 방출합니다.
CombineLatest 은 Observable이 새로운 시퀀스를 방출하는것을 기다려주지 않습니다.
그런데 zip 은 순서에 아주 엄격한 연산자 라고 Rx 개발자 분들이 설명을 합니다.
사진에서 보다 싶이 방출(Emit) 과 관계 없이 index 로 결합 됩니다.
일단 코드로 먼저 살펴보겠습니다
let number = Observable.of("1","2","3")
let alphabet = Observable.of("A","B","C")
Observable.zip(number, alphabet) { num, alpha in
"\(num + alpha)"
}.subscribe(onNext : {
print($0)
}).disposed(by: disposeBag)
//print
1A
2B
3C
amb 는 여러 Observable 을 관찰할수 있지만 방출이 시작되면
인자로 들어가는 Observable 의 방출을 무시합니다.
let number = PublishSubject<String>()
let alphabet = PublishSubject<String>()
number.amb(alphabet)
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
number.onNext("1")
number.onNext("2")
alphabet.onNext("A")
number.onNext("3")
alphabet.onNext("B")
// print
1
2
3
두 Observable중 첫번째 Observable에서 아이템이 방출될 때마다 그 아이템을 두번째 Observable의 가장 최근 아이템과 결합해 방출합니다.
let disposeBag = DisposeBag()
let numSubject = PublishSubject<Int>()
let charSubject = PublishSubject<String>()
numSubject
.withLatestFrom(charSubject) { "\($0)\($1)"}
.subscribe(onNext: { print($0) })
.disposed(by:disposeBag)
numSubject.onNext(1) // 아이템 방출되지 않음 : charSubject에서 방출된 아이템이 아직 1개도 없으므로
charSubject.onNext("A")
numSubject.onNext(2) // 2A
charSubject.onNext("B")
charSubject.onNext("C")
charSubject.onNext("D")
numSubject.onNext(3) // 3D
numSubject.onNext(4) // 4D
numSubject.onNext(5) // 5D
2A
3D
4D
5D
numSubject에서 아이템이 방출될때만 withLatestFrom 연산이 수행됩니다. 첫번째 Observable에서 첫 아이템(A)이 방출된 시점에 두번째 Observable에서 방출된 아이템이 없으므로, 아무런 아이템이 방출되지 않고 그 이후 부터 아이템이 방출되는 것을 확인할 수 있습니다.