이번 포스팅에서 살펴볼 연산자는 Observable을 통해 방출되는 item들을 변형해주는 연산자에 대해 알아보자.
toArray
onCompleted 이벤트 전까지, next 이벤트를 통해 전달된 데이터를 배열로 묶어 한번에 방출한다.
toArray() 연산자를 거치게 되면, Single 타입으로 바뀌게 된다.
Single 타입은 success, failture이벤트만 전달하기 때문에,
subscribe 메서드에서 onCompleted, onNext 가 아닌 onSuccess, onFailure를 통해 이벤트를 받는다.
let publish = PublishSubject<String>()
publish
.toArray() // Single 타입으로 리턴됨.
.subscribe(
onSuccess: { print($0) }
)
.disposed(by: disposeBag)
publish.onNext("A")
publish.onNext("B")
publish.onNext("C")
publish.onCompleted()
// onCompleted()를 호출 하지 않으면 Subscriber에 아무런 요소를 전달받지 못한다.
/* Prints:
["A", "B", "C"]
*/
map
Observable에서 동작하는 연산자라는 것만 제외하면, Foundation에서 제공하는 map과 같다.
publish
.map { $0 + "!!" }
.subscribe(
onNext: { print($0) },
onCompleted: { print("Completed") }
)
.disposed(by: disposeBag)
/* Prints:
A!!
B!!
C!!
*/
enumerated
해당 연산자를 통해, next 이벤트를 통해 방출되는 데이터와 index값을 같이 얻을 수 있다.
publish
.enumerated()
.map { index, element in //index와 element를 받아옴
if index % 2 == 0 {
return element + "!!"
}
return element
}
.subscribe(
onNext: { print($0) },
onCompleted: { print("Completed") }
)
.disposed(by: disposeBag)
/* Prints:
A!!
B
C!!
*/
flatMap
flatMap은 Observable에서 방출되는 요소들을
새로운 요소를 방출시키는 Observable에 투영하고,
이들을 하나의 Observable로 병합하여 구독자에게 넘겨준다.
flatMap의 핵심은 새로운 Observable을 생성한다는 것이다.
let alphabetPublish = PublishSubject<String>()
let koreanPublish = PublishSubject<String>()
let flatPublish = PublishSubject<Observable<String>>()
flatPublish
.flatMap { $0 }
.subscribe(
onNext: { print($0) },
onCompleted: { print("Completed") }
)
.disposed(by: disposeBag)
flatPublish.onNext(alphabetPublish) //alphabetPublish 전달 시점
alphabetPublish.onNext("A")
alphabetPublish.onNext("B")
flatPublish.onNext(koreanPublish) //koreanPublish 전달 시점
koreanPublish.onNext("가")
koreanPublish.onNext("나")
alphabetPublish.onNext("C")
koreanPublish.onNext("다")
/* Prints:
A
B
가
나
C
다
*/
위의 동작을 살펴보면,
flatPublish는 alphabetPublish과koreanPublish을 onNext로 전달하게 된다.
alphabetPublish과 koreanPublish flatMap의 함수를 거친 후,
각각 Observable의 요소들은 Observable에 투영되고, 하나의 Observable로 병합된다.
이를 통해 alphabetPublish과 koreanPublish에서 방출되는 요소들은
flatMap을 통해, 하나의 시퀀스(Observable)로 요소가 구독자에게 전달된다.
이를 마블 다이어 그램으로 살펴보면 다음과 같다.
그렇다면, map과 다른점은 무엇일까?
해당 flatPublish에 map연산을 진행하면, 아래와 같은 결과를 얻는다.
flatPublish
.map { $0 } // 1.
.subscribe( // 2.
onNext: { print($0) },
onCompleted: { print("Completed") }
)
.disposed(by: disposeBag)
/* Prints:
RxSwift.PublishSubject<Swift.String>
RxSwift.PublishSubject<Swift.String>
*/
1. map 연산은 closure내부의 함수를 실행하는 것뿐이기 때문에, publishSubject자체에 대한 연산이 수행된다.
2. subscribe의 onNext 의 element는 publishSubject가 된다.
반면, flatMap은 closure내부의 함수를 실행하는 것뿐만이 아닌,
Observable을 통해 방출되는 요소들을 하나의 Observable을 통해 방출하기 때문에,
2. 에서 subscribe의 onNext 의 element는 하나의 Observable을 통해 방출된 element가 된다.
flatMapLatest
flatMapLatest는 Observable에서 방출되는 요소들을
새로운 요소를 방출 시키는 Observable에 투영한다.
여기까지는 flatMap과 동일하지만,
flatMap은 하나의 Observable로 병합했다면,
flatMapLatest는 이들 중 가장 최근의 Observable에서만 값을 생성한다.
flatPublish
.flatMap { $0 }
.subscribe(
onNext: { print($0) },
onCompleted: { print("Completed") }
)
.disposed(by: disposeBag)
flatPublish.onNext(alphabetPublish) // 1. alphabetPublish 전달 시점
alphabetPublish.onNext("A")
alphabetPublish.onNext("B")
flatPublish.onNext(koreanPublish) // 2. koreanPublish 전달 시점
koreanPublish.onNext("가")
koreanPublish.onNext("나")
alphabetPublish.onNext("C")
koreanPublish.onNext("다")
/* Prints:
A
B
가
나
다
*/
flatMap과 다르게 C가 출력이 되지 않는다.
이는 "2. koreanPublish 전달 시점" 이전까진, alphabetPublish가 가장 최근의 Observable Sequence이기 때문에,
A B가 방출된다.
"2. koreanPublish 전달 시점" 이후로는, koreanPublush가 가장 최근의 Observable Sequence가 되기 때문에,
alphabetPublish 에서 방출하는 요소는 더 이상 방출되지 않고,
koreanPublush에서 방출하는 요소만 방출된다.
따라서, koreanPublish를 onNext로 전달하는 시점 이후로 alphabetPublish에서 전달하는 element들은 전달받지 못한다.
materialize & dematerialize
이 두 연산자는 RxSwift에서 에러 처리를 도와주는 연산자들이다.
materialize는 이벤트 타입으로 감싸서 넘겨주게 되어 Observable<Type> 이 Observable<Event<Type>>으로 바뀌게 된다.
dematerialize는 이벤트 타입으로 감싸진것들을 다시 풀어준다.
즉, Observable<Event<Type>> 이 Observable<Type>으로 바뀌게 된다.
우선 아래 두 코드를 먼저 살펴보자.
enum MyError: Error {
case someError
}
...
flatPublish
.flatMap { $0}
.materialize()
.subscribe (
onNext: { print($0) },
onCompleted: { print("Completed") }
)
.disposed(by: disposeBag)
flatPublish.onNext(alphabetPublish) //alphabetPublish 전달 시점
alphabetPublish.onNext("A")
alphabetPublish.onNext("B")
alphabetPublish.onError(MyError.someError)
flatPublish.onNext(koreanPublish) //koreanPublish 전달 시점
koreanPublish.onNext("가")
koreanPublish.onNext("나")
alphabetPublish.onNext("C")
/* Prints:
next(A)
next(B)
error(someError)
Completed
*/
...
flatPublish
.flatMap { $0.materialize() }
.subscribe (
onNext: { print($0) },
onCompleted: { print("Completed") }
)
.disposed(by: disposeBag)
...
/* Prints:
next(A)
next(B)
error(someError)
next(가)
next(나)
next(다)
*/
첫번째 코드는 flatMap이후에 materialize를 진행하였기 때문에, 병합된 Sequence에서 Error 이벤트 발생으로 해당 Sequence 죽게 된다.
하지만, 두 번째 코드에서는 각각의 Sequence에 대해 materialize를 진행하였기 때문에, 병합된 Sequence는 죽지 않고, alphabetPublish의 Sequence만 죽게 된다.
아래와 같이 materialize되어 Observable<Event<String>> 타입을
dematerialize를 통해 Observable<String>으로 풀어준다.
...
flatPublish
.flatMap { $0.materialize() }
.filter {
guard $0.error == nil else {
print("에러 발생")
return false
}
return true
}
.dematerialize()
.subscribe (
onNext: { print($0) },
onCompleted: { print("Completed") }
)
.disposed(by: disposeBag)
...
/* Prints:
A
B
에러 발생
가
나
다
*/
Reference
https://www.notion.so/Wallaby-RxSwift-72194669a39a4557baa69c672268af38
'iOS > RxSwift' 카테고리의 다른 글
[RxSwift] Operator(3) - Combining (0) | 2023.01.08 |
---|---|
[RxSwift] Traits (0) | 2022.12.28 |
[RxSwift] Operator(1) - Filtering (1) | 2022.12.25 |
[RxSwift] Subjects (0) | 2022.12.01 |
[RxSwift] Observable(2) - Creating Observable (0) | 2022.11.28 |