Combining Operators

DEVJUN·2024년 5월 2일
0

RxSwift

목록 보기
7/9
post-thumbnail

1. StartsWith (옵저버블이 요소를 방출하기 전에 다른 항목들을 앞부분에 추가하는 연산자)

![]

let bag = DisposeBag()
let numbers = [1, 2, 3, 4, 5]

Observable.from(numbers)
    .startWith(-1, -2)
    .startWith(-3)
    .startWith(0)
    .subscribe { print($0) }
    .disposed(by: bag)
    
================================
결과
next(0)
next(-3)
next(-1)
next(-2)
next(1)
next(2)
next(3)
next(4)
next(5)
completed

startsWith()는 주로 기본값이나 시작 값 지정시에 활용한다. 기존 값 앞에 값이 추가된다.
위 코드에서 결과를 보면 LIFO이다. 가장 나중에 추가한 값이 가장 먼저 방출된다!

2. Concat (두 개의 옵저버블을 연결하는 연산자 -> 먼저 온 Observable 방출 후 나중에 온 Observable 방출)

let bag = DisposeBag()
let fruits = Observable.from(["🍏", "🍎", "🥝", "🍑", "🍋", "🍉"])
let animals = Observable.from(["🐶", "🐱", "🐹", "🐼", "🐯", "🐵"])

// 타입 메서드 사용
Observable.concat([fruits, animals])
    .subscribe { print($0) }
    .disposed(by: bag)

// 인스턴스 메서드 사용
fruits.concat(animals)
    .subscribe { print($0) }
    .disposed(by: bag)


animals.concat(fruits)
    .subscribe { print($0) }
    .disposed(by: bag)
    
===================================
결과
next(🍏)
next(🍎)
next(🥝)
next(🍑)
next(🍋)
next(🍉)
next(🐶)
next(🐱)
next(🐹)
next(🐼)
next(🐯)
next(🐵)
completed
next(🍏)
next(🍎)
next(🥝)
next(🍑)
next(🍋)
next(🍉)
next(🐶)
next(🐱)
next(🐹)
next(🐼)
next(🐯)
next(🐵)
completed
next(🐶)
next(🐱)
next(🐹)
next(🐼)
next(🐯)
next(🐵)
next(🍏)
next(🍎)
next(🥝)
next(🍑)
next(🍋)
next(🍉)
completed

concat 연산자는 단순히 두 개의 옵저버블을 연결한다. 연결된 모든 옵저버블이 방출되는 요소들이 방출 순서대로 정렬되는 것이 아니라 이전 옵저버블이 모든 요소를 방출하고, Completed를 전달해야 이후 옵저버블이 방출을 시작한다.

3. merge (여러 옵저버블이 방출하는 항목들을 하나의 옵저버블에서 방출하도록 병합하는 연산자 -> 순서는 번갈아 가며)

let bag = DisposeBag()

enum MyError: Error {
   case error
}

let oddNumbers = BehaviorSubject(value: 1)
let evenNumbers = BehaviorSubject(value: 2)
let negativeNumbers = BehaviorSubject(value: -1)


let source = Observable.of(oddNumbers, evenNumbers, negativeNumbers)

source
    .merge(maxConcurrent: 2)
    .subscribe { print($0) }
    .disposed(by: bag)

oddNumbers.onNext(3)
evenNumbers.onNext(4)

negativeNumbers.onNext(-22)
evenNumbers.onNext(6)
oddNumbers.onNext(5)

negativeNumbers.onNext(-2)

oddNumbers.onCompleted()

===============================
결과
next(1)
next(2)
next(3)
next(4)
next(6)
next(5)
next(-2)

위 코드에서 maxConcurrent를 통해 2로 제한했기 때문에 처음엔 oddNumbers와 eventNumbers만 병합대상이 되어 방출된다. 이후 oddNumbers가 종료되더라도 negativeNumbers가 BehaviorSubject이기 때문에 가장 최근에 전달된 next 이벤트가 바로 구독자에게 전달되어 마지막에 -2가 전달될 수 있는 것이다.

4. combineLatest (소스 옵저버블이 방출하는 최신 요소를 병합하는 연산자)

combineLatest여러 소스 중에서 단 한 가지라도 이벤트를 방출하면 각각 소스의 마지막 값을 뽑아서 새로운 값을 방출한다.
한 번 값을 방출한 이후에는 클로저가 각각의 Observable이 방출했었던 최신 값을 받는다.

combineLatest는 이메일과 비밀번호가 변할 때마다 버튼의 enable를 계산할 때 사용할 수 있다.

let bag = DisposeBag()

enum MyError: Error {
   case error
}

let greetings = PublishSubject<String>()
let languages = PublishSubject<String>()

Observable.combineLatest(greetings, languages) { lhs, rhs -> String in
    return "\(lhs) \(rhs)"
}
.subscribe { print($0) }
.disposed(by: bag)

greetings.onNext("Hi")
languages.onNext("RxSwift")

greetings.onNext("Hello")
languages.onNext("World")

//greetings.onCompleted()
greetings.onError(MyError.error)
languages.onNext("SwiftUI")

languages.onCompleted() // 둘 다 onCompleted가 되어야 completed됨


===============================
결과
next(Hi RxSwift)
next(Hello RxSwift)
next(Hello World)
error(error)

위 코드에서 처음 부분을 보면 greetings, language Subject가 있고 greetings가 next를 통해 Hi를 전달하고, language Subject로 next 이벤트를 통해 RxSwift를 전달하면 위의 combineLatest 블록이 실행되어 Hi RxSwift가 전달된다.

마지막에 Source Observable에 completed이벤트 대신 에러 이벤트를 전달하는 경우에는 구독자에게 에러 이벤트를 전달하고 종료되어 버린다.

5.zip (발생 순서가 같은 이벤트만 발생 -> 순서가 다르면 발생 X)

zip연산자는 source 옵저버블이 방출하는 요소를 결합한다는 점에서 combineLatest와 같지만 차이점은
combineLatest는 source observable이 최신 요소라면 계속 사용되지만 zip 연산자는 그렇지 않고 클로저에게 중복된 요소를 전달하지 않는다.

// MARK: - CombineLatest와 달리 중복된 클로저에게 중복된 요소를 전달하지 않음

let bag = DisposeBag()

enum MyError: Error {
   case error
}

let numbers = PublishSubject<Int>()
let strings = PublishSubject<String>()

Observable.zip(numbers, strings) { "\($0) - \($1)" }
    .subscribe { print($0) }
    .disposed(by: bag)

numbers.onNext(1)
strings.onNext("one")
strings.onNext("two")
===============================
결과
next(1 - one)

=============================================================

let bag = DisposeBag()

enum MyError: Error {
   case error
}

let numbers = PublishSubject<Int>()
let strings = PublishSubject<String>()

Observable.zip(numbers, strings) { "\($0) - \($1)" }
    .subscribe { print($0) }
    .disposed(by: bag)

numbers.onNext(1)
strings.onNext("one")
strings.onNext("two")
numbers.onNext(2)
===============================
결과
next(1 - one)
next(2 - two)

위 코드의 첫 번째 코드에 대한 결과를 보면 1 - one 만 전달되는 것을 볼 수 있다. 만약 combineLatest라면 1 - one, 1 - two가 전달되었을 것이다.

두 번째 코드에 대한 결과를 보면 비로소 2 - two도 출력된 것을 볼 수 있다.즉 소스 옵저버블이 짝이 맞아야 구독자에 전달된다.

error 이벤트의 동작은 combineLatest와 같다. 하나의 소스 옵저버블에서만 error 이벤트가 발생하더라도 즉시 error 이벤트를 구독자에 전달하고 종료된다.

6. withLatestFrom (combineLatest와 다르게 각기 두개의 Observable을 합성하는 동일점은 있지만 첫번째 Observable의 이벤트가 발생해야 묶어 아웃풋을 전달)

withLatestFrom 연산자는 회원가입 버튼을 탭하는 시점에 텍스트 필드의 값을 가져오는 기능을 구현할 때 활용 가능하다.

let bag = DisposeBag()

enum MyError: Error {
   case error
}

let trigger = PublishSubject<Void>()
let data = PublishSubject<String>()

trigger.withLatestFrom(data)
    .subscribe { print($0) }
    .disposed(by: bag)

data.onNext("Hello")
trigger.onNext(())
trigger.onNext(())

data.onCompleted()
trigger.onNext(())

trigger.onCompleted()
=============================
결과
next(Hello)
next(Hello)
next(Hello)
completed

위 코드의 결과를 보면 trigger Subject에서 next이벤트를 방출해야 data Subject로 전달한 next 이벤트가 구독자에 전달된다.

또한 신기하게도 data Subject에 completed 이벤트를 전달하면 보통은 completed가 전달되고 종료되어야 하는데 그렇지 않고 마지막으로 전달한 next이벤트인 Hello가 전달된다.

하지만 trigger subject에 completed 이벤트를 전달하면 드디어 completed 이벤트가 바로 구독자로 전달된다. error 이벤트 또한 마찬가지로 trigger subject에서 error 이벤트를 전달하면 구독자가 직접 받게된다.



KXCoding 강의
Marble Diagram
ReactiveX 사이트

profile
🧑🏻‍💻iOS

0개의 댓글