티스토리 뷰

RxSwift Combining Operators

Combining Operators

  • 다양한 방법으로 sequence들을 모으고, 각각의 sequence내의 데이터들을 병합하는 방법을 배워보자.
  • observable로 작업할 때 가장 중요하게 확인해야 할 것은 observer가 초기값을 받는지 여부다.

.startWith()

  • 요녀석은 초기값을 갖는 옵저버블 이라고 할수있겠다.
  • 이벤트가 발생되어 next로 방출될때 초기값을 방출한후 전달된 이벤트들을 방출할수 있게된다.
  • 네트워크 연셜 상태 같이 현재 상태가 필요한 상황에 요녀석을 사용해 현재 상태를 초기값으로 붙여줄수 있겠다.
    let numbers = Observable.of(2, 3, 4)
    

let observable = numbers.startWith(1)
observable.subscribe(onNext: {
print($0)
})

- 요롷게 startWith(1)하면 1, 2, 3, 4 순서대로 출력됨니다!
- numbers에 초기값을 1로넣어주었기 때문이지요!

- 좀더 자세히 설명하자면 startWith()는 Observable sequence를 이어붙이는거다. startWith() 의 위치와 상관없이 맨 앞부분에 sequence를 이어붙이기 때문에 추후 어떤 업데이트가 있더라도 초기값을 즉시 얻을수 있는 Oberverble을 보장한다.

### Observable.concat(_:) 타입오퍼레이터
![](https://i.imgur.com/Vpzapmu.png)

- 방금 startWith 예제로 구현한 것은 하나의 값을 갖는 sequence를 다른 sequence에 연결한 것이다.
- Observable.concat(_:)을 통해서는 두개의 sequence를 묶을 수 있다.
```swift
let first = Observable.of(1, 2, 3)
let second = Observable.of(4, 5, 6)

let observable = Observable.concat([first, second])
observable.subscribe(onNext: {
    print($0)
})
  • 예상한대로 observable2개를 붙였기때문에(하나의 sequence가됨)1, 2, 3, 4, 5, 6 순서대로 방출된다!

  • 첫 번째 콜렉션의 sequence의 각 요소들이 완료될 때까지 구독하고, 이어서 다음 sequence를 같은 방법으로 구독한다. 이러한 과정은 콜렉션의 모든 observable 항목이 사용될 때까지 반복된다.

  • 만약에 내부의 observable의 어떤 부분에서 에러가 방출되면, concat된 observable도 에러를 방출하며 완전 종료된다.

.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)
})
  • 요녀석은 인스턴스 오퍼레이터 이기때문에 로드된 인스턴스에서 사용할수있으며 기존 Observable이 완료될때까지 기다린후 다음 Observable을 구독한다.
  • Observable의 결합은 반드시 두 Observable의 요소들이 같은 타입일 때 가능하다. 다른 타입의 Observable을 합치려고하면 컴파일 에러가 발생된다.

.concatMap(_:)

  • flatMap과 밀접한 관련이 있다.
  • flatMap을 통과하면 Observable sequence가 구독을 위해 리턴되고, 방출된 observable들은 합쳐지게 된다.
  • concatMap은 각각의 sequence가 다음 sequence가 구독되기 전에 합쳐진다는 것을 보증한다.
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)
})
  1. 옵저버블 딕셔너리가 있다.
  2. "Germany", "Spain"을 방출하는 옵저버블이 있는데 내부에서 방출된 값을 옵저버블 딕셔너리의 키값과 매핑한다.
  3. 딕셔너리에 저장된 value들을 방출하게 한다.
  4. 결과적으로 sequences에 저장된 두개의 옵저버블을 하나로 합친다.

.merge()

  • sequence를 합치는 가장 쉬운 방법.
  • 여러 Observable에 공통된 로직을 실행해야 할 때 merge 해서 Subscribe 할 수 있습니다.
  • 위의 이미지만 봐도 2개의 sequence를 합친모습을 볼수있는데 이때 중요한것은 방출되는 순서도 적용된다는 점이다.
let disposeBag = DisposeBag()

let first = Observable.of(1, 2, 3)
let second = Observable.of(4, 5, 6)

Observable.merge(first, second)
    .subscribe(onNext: { print($0) })
    .disposed(by: dispseBag)

/* Prints:
1
4
2
5
3
6
*/

.merge(maxConcurrent:)

  • 합칠 수 있는 sequence의 수를 제한하기 위해서 merge(maxConcurrent:)를 사용할 수 있다.
  • limit에 도달한 이후에 들어오는 observable을 대기열에 넣는다. 그리고 현재 sequence 중 하나가 완료되자마자 구독을 시작한다.
  • 이러한 제한 메소드를 merge()보다 덜 사용하게 될 가능성이 크다. 하지만 적절한 용도가 있다는 것을 항상 기억해두자. 네트워크 요청이 많아질 때 리소스를 제한하거나 연결 수를 제한하기 위해 merge(maxConcurrent:) 메소드를 쓸 수 있다.

.combineLatest(::resultSelector:)

  • 여러 Observable에서 가장 최신의 값을 병합하여 방출한다.
  • 모든 Observable이 하나의 값을 방출하는 순간까지 아무일도 일어나지 않는다.
  • 묶여질 두개의 옵저버블의 타입은 같지 않아도된다.
  • 한 번 값을 방출한 이후에는 클로저가 각각의 Observable이 방출하는 최신의 값을 받는다.
let disposeBag = DisposeBag()

let first = Observable.of(1, 2, 3, 4)
let second = Observable.of("A", "B", "C")

Observable.combineLatest(first, second)
    .subscribe(onNext: { print("\($0)" + $1) })
    .disposed(by: dispseBag)

/* Prints:
1A
2A
2B
3B
3C
4C
*/

2. combineLatest(,,resultSelector:)

  • 이들은 2개부터 8개까지의 observable sequence를 파라미터로 가진다.
  • 앞서 언급한대로, sequence 요소의 타입이 같을 필요는 없다.
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) })    

.zip

  • 발생 순서가 같은 Event 끼리 병합하여 방출합니다.
  • 이벤트끼리 쌍을 이루지 않으면 방출하지 않습니다.
let disposeBag = DisposeBag()

let first = Observable.of(1, 2, 3, 4)
let second = Observable.of("A", "B", "C")

Observable.zip(first, second)
    .subscribe(onNext: { print("\($0)" + $1) })
    .disposed(by: dispseBag)

/* Prints:
1A
2B
3C
*/

.withLatestFrom(_:)

  • 한쪽 Observable의 이벤트가 발생할 때 두개의 Observable을 병합해줍니다.
  • 아래 예제에서는 버튼을 탭할시(Void)이벤트가 발생할 때 String과 병합한다.
let button = PublishSubject<Void>()
let textField = PublishSubject<String>()

let observable = button.withLatestFrom(textField)
_ = observable.subscribe(onNext: { print($0) })

textField.onNext("Par")
textField.onNext("Pari")
textField.onNext("Paris")
button.onNext(())
button.onNext(())

/* Prints:
paris
paris
*/

.sample(_:)

  • withLatestFrom(_:)과 거의 똑같이 작동하지만, 한 번만 방출한다. 즉 여러번 새로운 이벤트를 통해 방아쇠 당기기를 해도 한번만 출력되는 것.
let button = PublishSubject<Void>()
let textField = PublishSubject<String>()

let observable = textField.sample(button)
_ = observable.subscribe(onNext: { print($0) })

textField.onNext("Par")
textField.onNext("Pari")
textField.onNext("Paris")
button.onNext(())
button.onNext(())
/* Prints:
paris
*/
  • 위의 예시코드에서 중간부분만 변경해두었다.
  • 요러면 paris가 한번만 출력된다.

.amb(_:)

  • amb(_:)에서 amb는 ambiguous(모호한) 이라 생각하면 된다.
  • 두가지 sequence의 이벤트 중 어떤 것을 구독할지 선택할 수 있게 한다.
let left = PublishSubject<String>()
let right = PublishSubject<String>()

let observable = left.amb(right)
let disposable = observable.subscribe(onNext: { value in
     print(value)
})

left.onNext("Lisbon")
right.onNext("Copenhagen")
left.onNext("London")
left.onNext("Madrid")
right.onNext("Vienna")

disposable.dispose()
  • left와 right를 사이에서 모호하게 작동할 observable을 만든다.
  • 두 개의 observable에 모두 데이터를 보낸다.
  • amb(_:) 연산자는 left, right 두 개 모두의 observable을 구독한다.
  • 그리고 두 개중 어떤 것이든 요소를 모두 방출하는 것을 기다리다가 하나가 방출하면 처음 작동한 observable에 대해서만 요소들을 늘어놓는다.
  • 처음에는 어떤 sequence에 관심이 있는지 알 수 없기 때문에, 일단 시작하는 것을 보고 결정하는 것이다.

.switchLatest()

  • observable로 들어온 마지막 sequence의 아이템만 구독한다.
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!
*/

.reduce(::)

  • Swift 표준 라이브러리의 reduce(::) 처럼 내부의 요소들을 결합 시켜준다.
    let source = Observable.of(1, 3, 5, 7, 9)
    

let observable = source.reduce(0, accumulator: +)
observable.subscribe(onNext: { print($0) } )

 //위의 녀석을 풀어쓰자면 이러한 의미다!

let observable2 = source.reduce(0, accumulator: { summary, newValue in
return summary + newValue
})

observable2.subscribe(onNext: { print($0) })
/* Prints:
1
4
9
16
25
*/


### .scan(_:accumulator:)
- 값을 저장해 가지고 있을 수 있고, 그 값을 통해 이벤트를 변형할 수 있다.
- 변형하는 이벤트의 타입은 원본 이벤트 타입과 같아야 한다.
- 초기값을 지정해야 한다.
- reduce(:_:_) 처럼 작동하지만, 리턴값이 Observable이다.

```swift
let source = Observable.of(1, 3, 5, 7, 9)

let observable = source.scan(0, accumulator: +)
observable.subscribe(onNext: { print($0) })

/* Prints:
1
4
9
16
25
*/
  • scan(_:accumulator:)의 쓰임은 광범위 하다. 총합, 통계, 상태를 계산할 때 등 다양하게 쓸 수 있다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함