toArray
이 연산자는 옵저버블이 방출하는 모든 요소를 배열에 담은 다음 이 배열을 방출하는 옵저버블을 생성한다.
이 연산자는 별도의 파라미터를 받지 않는다.
하나의 요소를 방출하는 옵저버블로 변환하는데 더 정확히는 Single로 변환한다.
Single는 하나의 요소를 방출하거나 ErrorEvent를 전달하는 특별한 옵저버블이다.
하나의 요소를 방출한 다음 바로 종료된다.
let subject = PublishSubject<Int>()
subject
.toArray()
.subscribe{ print($0) }
.disposed(by: disposeBag)
subject.onNext(1)
방출한 숫자가 구독자로 바로 전달되지 않는다.
toArray는 소스 옵저버블이 방출하는 모든 요소를 하나의 배열에 담는다.
소스 옵저버블이 더이상 요스를 방출하지 않는 시점이 되어야 모든 요소를 배열에 담을 수 있다.
그래서 소스 옵저버블이 종료되기 전까지 구독자에게 전달되지 않는다.
subject
.toArray()
.subscribe{ print($0) }
.disposed(by: disposeBag)
subject.onNext(1)
subject.onNext(2)
subject.onNext(3)
subject.onCompleted()
//success([1, 2, 3])
서브젝트가 종료된다면 소스 옵저버블이 방출한 모든 요소를 담아 전달한다.
map
map은 옵저버블이 배출하는 항목을 대상으로 함수를 실행한다. 그러고 실행결과를 방출하는 옵저버블을 리턴한다.
map연산자는 클로저를 파라미터로 받는다.
이름 앞에 hello를 붙혀서 리턴하도록 만든다.
let skills = ["Swift", "SwiftUI", "RxSwift"]
Observable.from(skills)
.map{ "Hello, \($0)" }
.subscribe{print($0)}
//next(Hello, Swift)
//next(Hello, SwiftUI)
//next(Hello, RxSwift)
//completed
기존 문자열 앞에 Hello가 붙은 문자열을 방출한다.
map연산자를 활용하다 보면 파라미터와 동일한 형식을 리턴해야 한다고 생각하는 경우가 많다.
하지만 이런 제약은 없다.
compactMap
옵저버블이 방출하는 이벤트에서 값을 꺼낸다음 옵셔널 형태로 바꾸고 원하는 변환을 실행한다. 최종 변환결과가 nil이면 해당 이벤트는 전달하지 않고 필터링한다.
let disposeBag = DisposeBag()
let subject = PublishSubject<String?>()
subject
.subscribe { print($0) }
.disposed(by: disposeBag)
//서브젝트로 0.3초마다 랜덤으로 별과 nil을 전달한다.
Observable<Int>.interval(.milliseconds(300), scheduler: MainScheduler.instance)
.take(10)
.map { _ in Bool.random() ? "⭐️" : nil }
.subscribe(onNext: { subject.onNext($0) })
.disposed(by: disposeBag)
만약 여기서 nil을 필터링하고 싶다면 이렇게 하면 될것이다.
subject
.filter { $0 != nil }
.subscribe { print($0) }
.disposed(by: disposeBag)
이렇게 하면 nil은 필터링 되는데 구독자로 전달되는 이벤트는 여전히 옵셔널이라 값을 쓸 때는 언래핑이 필요하다.
만약 언래핑된 이벤트를 구독자로 전달하고 싶다면
subject
.filter { $0 != nil }
.map{ $0! }
.subscribe { print($0) }
.disposed(by: disposeBag)
!을 사용해 강제추출할 수 있다. 그러면 구독자는 언래핑된 데이터를 전달받는다.
이렇게 필터와 맵을 사용해도 되지만 compactMap을 사용하면 코드가 단순해진다.
subject
.compactMap{$0}
.subscribe { print($0) }
.disposed(by: disposeBag)
nil은 필터링되고 값은 언래핑되어서 방출된다.
flatMap
원본 옵저버블이 이벤트를 방출하면 flatMap연산자가 변환함수를 실행한다.
변환함수는 nextEvent에 포함된 값을 원하는 형태로 바꾸거나 이 값을 활용해서 원하는 작업을 실행한다.
그런다음 결과를 새로운 옵저버블을 통해 방출한다.
원본 옵저버블에서 빨간색 원을 방출하면 flaatMap은 같은 색을 가진 마름모 두개로 바꾸고새로운 옵저버블을 통해 방출한다.
그리고 이어지는 초록색 원과 파란색 원도 마찬가지이다.
이렇게 만들어진 옵저버블은 보통 Inner Observable이라고 부른다. (그림에서 가운데 네모)
이름처럼 내부적으로 이어지는 옵저버블이고 외부에서는 존재를 알 필요가 없다.
최종적으로 구독자에게 이벤트를 전달할 떄에는 모든 옵저버블을 하나로 합친 새로운 옵저버블을 사용한다.
이 과정을 Falttening(평탄화)라고한다.
그리고 변환된 옵저버블(맨 아래)을 Result Observable이라고 부른다.
이렇게 개별 이벤트가 독립된 개별 inner Observable로 변환되었다가. 다시 Result Observable로 합쳐지기 때문에 처음에는 이해하기 어렵다. 여러 예제를 봐야한다.
예제에선 원을 마름모로 바꾸고 있지만 하트로 바꿔본다.
Observable.from([redCircle, greenCircle, blueCircle])
.flatMap{ circle -> Observable<String> in
//클로저로 전달되는 circle파라미터에는 nextevent의 값이 전달되는데
//여기에서는 빨간색, 초록색, 파란색 원이 순서대로 전달된다
//그리고 최종적으로 리턴하는 옵저버블에선 문자열을 방출한다.
}
Observable.from([redCircle, greenCircle, blueCircle])
.flatMap{ circle -> Observable<String> in
switch circle{
case redCircle:
return Observable.repeatElement(redHeart)
.take(5)
case greenCircle:
return Observable.repeatElement(greenHeart)
.take(5)
case blueCircle:
return Observable.repeatElement(blueHeart)
default:
return Observable.just("")
}
}
.subscribe{ print($0) }
.disposed(by: disposeBag)
스위치안에서 리턴하는 옵저버블은 이너옵저버블이고, flatMap은 모든 이너옵저버블을 합쳐서 하나의 Result Observable을 리턴한다.
그리고 구독자는 resultObservable을 통해서 이벤트를 받는다.
원을 받아서 하트 5개로 변환하는 flatMap이다.
실행하면 하트의 순서가 뒤죽박죽이다
flatMap은 innerObservable이 Event를 방출하면 Result Observable을 통해서 지연없이 방출한다. 그래서 빨간색 원이 처음 방출되고
이어서 빨간색 하트가 방출되었지만 나중에 초록색이나 파란색하트가 먼저 방출되는 경우도 있다.
이런 동작방식을 Interleaving이라고 한다.
flatMapFirst, flatMapLatest
flatMap에서 파생된 연산자다.
flatMapFirst
이 연산자의 파라미터는 flatMap과 동일하고 리턴형도 동일하다.
하지만 연산자가 리턴하는 resultObservable은 InnerObservable중에서 가장 먼저 이벤트를 방출하는 옵저버블의 이벤트만 방출하고 나머지 이너옵저버블은 무시한다.
Observable.from([redCircle, greenCircle, blueCircle])
.flatMapFirst { circle -> Observable<String> in
switch circle {
case redCircle:
return Observable.repeatElement(redHeart)
.take(5)
case greenCircle:
return Observable.repeatElement(greenHeart)
.take(5)
case blueCircle:
return Observable.repeatElement(blueHeart)
.take(5)
default:
return Observable.just("")
}
}
.subscribe { print($0) }
.disposed(by: disposeBag)
가장먼저 실행되는건 case redCircle: 이고 case redCircle: 에서 리턴하는 InnerObservable이 첫번째 옵저버블이고 가장 먼저 이벤트 방출을 시작한다 이후에 greenCircle이나 blueCircle이 방출되면 아래쪽에서 다른 이너옵저버블도 리턴하겠지만 flatMapFirst는 redCircle만 방출하고 나머지는 무시한다. 구독자는 결과적으로 redCircle이 방출하는 이벤트만 받게된다.
위 상황에선 3개의 case가 거의 동시에 옵저버블을 방출한다.
조금 다른 상황인 redcircle 3초뒤에 blueCircle을 방출시켜보면 red다음 blue가 방출된다.
주기를 살펴본다.
위의 예제
빨빨빨빨빨빨빨빨빨빨
초초초초초초초초초초
파파파파파파파파파파
거의 동시에 이뤄지기 때문에 가장 먼저 들어온 빨간색만 방출된다.
1초차이
빨빨빨빨빨빨빨빨빨빨
초초초초초초초초초초
파파파파파파파파파파
모두 같은 주기 안에 있으므로 빨간색만 방출된다.
5초차이
빨빨빨빨빨빨빨빨빨빨
초초초초초초초초초초
파파파파파파파파파파
초록색은 빨간색의 주기 안에 있지만 파란색은 겹친 주기 없이 가장 먼저 들어온 첫 번째 이너 옵저버블이 된다.
이 주기에서 첫 번째로 이벤트를 방출하는 이너 옵저버블이니까 방출하게된다.
주기별로 가장 먼저 들어온 이너옵저버블을 방출한다.
flatMapLatest
원본 옵저버블이 방출하는 이벤트를 이너옵저버블로 변환한다는 점에선 flatmap과는 같다.
하지만 모든 이너옵저버블이 방출하는 이벤트를 하나로 병합하진 않는다. 원본 옵저버블이 이벤트를 방출하고 새로운 이너옵저버블이 생성되면 기존에 있던 이너옵저버블은 이벤트 방출을 중단하고 종료된다.
이때부터 새로운 이너옵저버블이 이벤트 방출을 시작하고 ResultObservable은 이 옵저버블이 방출하는 옵저버블을 구독자로 전달한다.
Latest는 가장 최근에 생성된 옵저버블을 의미한다. 새로운 InnerObservable이 생성되면 기존의 InnerObservable은 바로 종료된다.
struct 높이뛰기선수: 선수{
var 점수: BehaviorSubject<Int>
}
let 서울 = 높이뛰기선수(점수: BehaviorSubject<Int>(value: 7))
let 제주 = 높이뛰기선수(점수: BehaviorSubject<Int>(value: 6))
let 전국체전 = PublishSubject<선수>()
전국체전
.flatMapLatest { 선수 in // 선수의 점수라는 시퀀스의 가장 최신 값만을 반영한다
선수.점수
}
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
전국체전.onNext(서울) // 여기서 서울의 점수의 최신값은 7이다 7을 뱉는다,.
서울.점수.onNext(9) // 여기서 9로 업데이트
전국체전.onNext(제주) // 여기서 제주의 점수의 최신값은 6이다 6을 뱉는다,. 그리고, 전국체전에 새로운 시퀀스인 제주 시퀀스가 추가되었으므로 서울시퀀스는 무시된다.
서울.점수.onNext(10)
제주.점수.onNext(8)
// 전국체전은 서울의 시퀀스와 제주의 시퀀스 2개의 시퀀스를 가지고있다. 그 2개의 시퀀스중 가장 최근에 생긴 시퀀스만을 내뱉는다.