-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Labels
Description
Ch.9 Combining Operators
A. 앞에 붙이기
1. startWith(_:)
let numbers = Observable.of(2, 3, 4)
let observable = numbers.startWith(1)
// startWith(_:)의 위치에 속지 말 것
observable
.subscribe(onNext: {
print($0)
// 1
// 2
// 3
// 4
})- "현재 위치"나 "네트워크 연결 상태" 같이 "현재 상태"가 필요한 상황이 있다. 이럴 때 현재 상태의 초기 값을 붙일 수 있다.
- startWith는 RxSwift의 결정론적 성질에 잘 부합한다. 추후에 어떤 업데이트가 있더라도 초기값을 즉시 얻을 수 있는 Observer를 보장
2 Observable.concat(_:)
let first = Observable.of(1, 2, 3)
let second = Observable.of(4, 5, 6)
// 순서가 지정된 observable 컬렉션 (array 같은)을 취한다.
let observable = Observable.concat([first, second])
observable.subscribe(onNext: {
print($0)
// 1
// 2
// 3
// 4
// 5
// 6
})- Observable.concat(_:)을 통해서는 두 개의 sequence를 묶을 수 있다
- 첫 번째 콜렉션의 sequence의 각 요소들이 완료될 떄까지 구독하고, 이어서 다음 sequence를 같은 방법으로 구독한다. 이러한 과정은 콜렉션의 모든 observable 항목이 사용될 때까지 반복한다
- 내부의 observable의 어떤 부분에서 에러가 방출되면, concat된 observable도 에러를 방출하며 완전 종료
3. concat(_:)
let germanCities = Observable.of("Berlin", "Münich", "Frankfurt")
let spanishCities = Observable.of("Madrid", "Barcelona", "Valencia")
let observable = germanCities.concat(spanishCities)
observable.subscribe(onNext: { print($0) })
// Berlin
// Münich
// Frankfurt
// Madrid
// Barcelona
// Valencia- 반드시 두 observable의 요소들이 같은 타입일 때 가능
- 다른 타입의 observable을 합치려고 하면 컴파일러 에러
4. concatMap(_:)
let sequences = [
"Germany": Observable.of("Berlin", "Münich", "Frankfurt"),
"Spain": Observable.of("Madrid", "Barcelona", "Valencia")
]
let observable = Observable.of("Germany", "Spain")
.concatMap({ country in
sequences[country] ?? .empty() })
_ = observable.subscribe(onNext: {
print($0)
// Berlin
// Münich
// Frankfurt
// Madrid
// Barcelona
// Valencia
})
}- flatMap(_:)과 유사
- flatMap의 경우, 통과하면 Observable sequence가 구독을 위해 리턴되고, 방출된 observable들은 합쳐지게 된다.
- concatMap의 경우, 각각의 sequence가 다음 sequence가 구독되기 전에 합쳐진다는 것을 보증한다.
- 결과적으로 print 값만 보면 두 연산자를 사용한 건 동일한 결과를 가진다
B. 합치기
1. merge()
let left = PublishSubject<String>()
let right = PublishSubject<Int>()
let source = Observable.of(
left.asObservable(),
right.asObservable()
)
let observable = source.merge()
let disposable = observable.subscribe(onNext: {
print($0)
})
var leftValues = ["Berlin", "Münich", "Frankfurt"]
var rightValues = ["Madrid", "Barcelona", "Valencia"]
repeat {
// repeat-while 블록을 1회 실행하고
// 루프 조건이 true면 반복, false면 해제
switch Bool.random() {
case true where !leftValues.isEmpty:
left.onNext("Left: " + leftValues.removeFirst())
case false where !rightValues.isEmpty:
right.onNext("Right: " + rightValues.removeFirst())
default:
break
}
// 논리 연산자 || : 둘 중 하나가 true면 true
} while !leftValues.isEmpty || !rightValues.isEmpty
// Subject는 완료되지 않았기 때문에, dispose()를 호출
disposable.dispose()- merge()는 observable들 요소로 가지고 방출하는 source observable을 취한다.
- 여러 sequence들을 합쳐서 하나의 sequence를 방출
- 이 때 sequence의 타입은 같아야 한다
- 합칠 수 있는 sequence의 수를 제한하기 위해서 merge(maxConcurrent:)를 사용 가능 - 네트워크 요청이 많아질 때 리소스를 제한하거나 연결 수를 제한
C. 결합하기
1. combineLatest(::resultSelector:)
// 예시 1) ----------
let left = PublishSubject<String>()
let right = PublishSubject<String>()
let observable = Observable.combineLatest(left, right, resultSelector: { lastLeft, lastRight in
"\(lastLeft) \(lastRight)"
})
let disposable = observable.subscribe(onNext: {
print($0)
})
// 결합된 observable이 하나의 값을 방출하기 전까지는 아무일도 일어나지 않는다.
print("> Sending a value to Left")
left.onNext("Hello,")
print("> Sending a value to Right")
right.onNext("world")
// 한번 값을 방출한 이후에는 클로저가 각각의 observable이 생성하는 최종의 값을 받게 된다.
print("> Sending another value to Right")
right.onNext("RxSwift")
print("> Sending another value to Left")
left.onNext("Have a good day,")
disposable.dispose()
/* Prints:
> Sending a value to Left
> Sending a value to Right
Hello, world
> Sending another value to Right
Hello, RxSwift
> Sending another value to Left
Have a good day, RxSwift
*/
// 예시 2) ----------
let choice:Observable<DateFormatter.Style> = Observable.of(.short, .long)
let dates = Observable.of(Date())
let observable = Observable.combineLatest(choice, dates, resultSelector: { (format, when) -> String in
let formatter = DateFormatter()
formatter.dateStyle = format
return formatter.string(from: when)
})
observable.subscribe(onNext: { print($0) })
// 4/18/22
// April 18, 2022
// 예시 3) ----------
let observable = Observable.combineLatest([left, right]) { strings in
strings.joined(separator: " ")
}- 예시 1
- 내부(결합된) sequence들은 값을 방출할 때마다, 제공한 클로저를 호출하며 각각의 내부 sequence들의 최종값을 받는다.
- 만약 2개의 sequence 중 1개만 값을 onNext로 전달된 상황이라면 resultSelector 클로저는 호출되지 않는다.
- 결합된 observable이 하나의 값을 방출하기 전까지는 아무일도 일어나지 않는다.
- 한번 값을 방출한 이후에는 클로저가 각각의 observable이 생성하는
최종의 값을 받게 된다. - 클로저의 리턴타입으로 observable을 생성하기에 map 처럼 타입 변환에 활용가능
- 일반적인 패턴은 값을 튜플에 결합한 다음 체인 아래로 전달
- 여러 TextField를 한번에 관찰하고 값을 결합하거나 여러 소스들의 상태들을 보는 것과 같은 경우 사용이 가능하다
- 예시 2
- 2개부터 8개까지의 observable sequence를 파라미터로 가질 수 있다
- 각 sequence 요소의
타입이 같을 필요는 없다
- 예시 3
- array내의 Observable의 최종 값들을 결합하여 활용하는 축약도 가능하다
2. zip
enum Weather {
case cloudy
case sunny
}
let left:Observable<Weather> = Observable.of(.sunny, .cloudy, .cloudy, .sunny)
let right = Observable.of("Lisbon", "Copenhagen", "London", "Madrid", "Vienna")
let observable = Observable.zip(left, right, resultSelector: { (weather, city) in
return "It's \(weather) in \(city)"
})
observable.subscribe(onNext: {
print($0)
// It's sunny in Lisbon
// It's cloudy in Copenhagen
// It's cloudy in London
// It's sunny in Madrid
})- 상기 코드에서 Vienna가 출력되지 않는다
- zip은 일련의 observable이 새 값을 각자 방출할 때까지 기다리다가, 둘 중 하나의 observable이라도 완료되면, zip 역시 완료된다
- 발생 순서가 같은 이벤트만 발생 - indexed sequencing
- 더 긴 observable이 남아있어도 기다리지 않는다
D. Triggers
여러개의 observable을 한번에 받는 경우, 다른 observable들로부터 데이터를 받는 동안 어떤 observable은 단순히 방아쇠 역할을 하는 것이 가능
1. withLatestFrom(_:)
let button = PublishSubject<Void>()
let textField = PublishSubject<String>()
let observable = button.withLatestFrom(textField)
_ = observable.subscribe(onNext: { print($0) })
// Pari
// Paris
// Paris
textField.onNext("Par")
textField.onNext("Pari")
button.onNext(())
textField.onNext("Paris")
button.onNext(())
button.onNext(())- button에 새 이벤트가 추가되기 직전에 textField가 추가된 최신 값인 Pari, Paris가 출력된다. 그 전의 값들은 무시된다
검색어 입력+서치 버튼과 같은 부분에 요긴하게 사용 가능
2. sample(_:)
let button = PublishSubject<Void>()
let textField = PublishSubject<String>()
// let observable = button.withLatestFrom(textField)
// _ = observable
// .distinctUntilChanged()
// .subscribe(onNext: { print($0) })
let observable = textField.sample(button)
_ = observable.subscribe(onNext: { print($0) })
// Pari
// Paris
textField.onNext("Par")
textField.onNext("Pari")
button.onNext(())
textField.onNext("Paris")
button.onNext(())
button.onNext(())- withLatestFrom(_:)과 거의 똑같이 작동하지만, 한 번만 방출한다. 즉 여러번 새로운 이벤트를 통해 방아쇠 당기기를 해도 한번만 출력
- withLatestFrom(_:)은 데이터 observable을 파라미터로 받고, sample(_:)은 trigger observable을 파라미터로 받는다. 실수하기 쉬운 부분이니 주의할 것
검색어 입력+서치 버튼과 같은 부분에 요긴하게 사용 가능
E. Switches
1. amb(_:)
let left = PublishSubject<String>()
let right = PublishSubject<String>()
let observable = left.amb(right)
let disposable = observable.subscribe(onNext: { value in
print(value)
})
// right와 left 중 어느 것이 방출되는 지에 따라 달라짐
// left.onNext("Lisbon")
right.onNext("Copenhagen")
left.onNext("London")
left.onNext("Madrid")
right.onNext("Vienna")
disposable.dispose()- left와 right를 사이에서 모호하게 작동할 observable을 만든다.
- 두 개중 어떤 것이든 요소를 모두 방출하는 것을 기다리다가 하나가 방출을 시작하면 나머지에 대해서는 구독을 중단한다
- 처음 작동한 observable에 대해서만 구독하며 요소들을 활용한다
- 처음에는 어떤 sequence에 관심이 있는지 알 수 없기 때문에, 일단 시작하는 것을 보고 결정
2. switchLatest()
let one = PublishSubject<String>()
let two = PublishSubject<String>()
let three = PublishSubject<String>()
let source = PublishSubject<Observable<String>>()
let observable = source.switchLatest()
let disposable = observable.subscribe(onNext: { print($0) })
source.onNext(one)
one.onNext("Some text from sequence one")
two.onNext("Some text from sequence two")
source.onNext(two)
two.onNext("More text from sequence two")
one.onNext("and also from sequence one")
source.onNext(three)
two.onNext("Why don't you see me?")
one.onNext("I'm alone, help me")
three.onNext("Hey it's three. I win")
source.onNext(one)
one.onNext("Nope. It's me, one!")
disposable.dispose()
/* Prints:
Some text from sequence one
More text from sequence two
Hey it's three. I win
Nope. It's me, one!
*/- source observable로 들어온 마지막 sequence의 아이템만 구독
- flatMapLatest(_:)와 유사, flatMapLatest(_:)는 observable의 마지막 값들을 매핑하여 구독
F. sequence내의 요소들 간 결합
1. reduce(::)
let source = Observable.of(1, 3, 5, 7, 9)
// 1
let observable = source.reduce(0, accumulator: +)
observable
.subscribe(onNext: { print($0) } )
// 25
// 2 - 1번의 축약을 풀어서 보면
let observable2 = source.reduce(0, accumulator: { summary, newValue in
return summary + newValue
})
observable2
.subscribe(onNext: { print($0) })
// 25- 더한 결과만 이벤트로 받고싶을 때 사용
2. scan(_:accumulator:)
let source = Observable.of(1, 3, 5, 7, 9)
let observable = source.scan(0, accumulator: +)
observable.subscribe(onNext: { print($0) })
// 1
// 4
// 9
// 16
// 25- reduce(:_:_) 처럼 작동하지만, 중간에 변화를 추적이 가능하다(Observable)
- 더할 때마다 증가된 값을 각각의 이벤트로 방출 - 더할 때마다 이벤트 받고싶을 때 사용
- 직전에 내려보낸 것과 상호작용
- 총합, 통계, 상태를 계산할 때 등 다양하게 쓸 수 있다










