Description
Ch.7 Transforming Operator
A. 변환연산자의 요소들
기본적으로 Observable은 독립적인 요소들을 방출하지만, table 또는 collection view와 바인딩 하기 위해서 해당 데이터를 조합하거나 변환하는 처럼 이를 활용할 수 있다
1. toArray
- Observable의 독립적 요소들을 array로 묶는 가장 편리한 방법
- observable sequence의 요소들은 array의 요소들로 넣고, 이렇게 구성된 array를 Single<[T]>타입으로 .next 이벤트를 통해 subscriber에게 방출
let disposeBag = DisposeBag()
Observable.of("A", "B", "C")
.toArray()
.subscribe(onNext: {
print($0)
// success(["A", "B", "C"])
})
.disposed(by: disposeBag)
2. map
- Swift 표준 라이브러리의 map과 동일, 원하는 타입 / 계산된 결과로 새로운 Obsservable 방출
let disposeBag = DisposeBag()
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
Observable<NSNumber>.of(123, 4, 56)
.map {
// formatter를 통한 NSNumber 타입을 String 타입 스펠링으로 변환
formatter.string(from: $0) ?? ""
}
.subscribe(onNext: {
print($0)
// one hundred twenty-three
// four
// fifty-six
})
.disposed(by: disposeBag)
3. enumerated
- 해당 요소들의 index와 값을 갖는 tuple을 만든다
let disposeBag = DisposeBag()
Observable.of(1, 2, 3, 4, 5, 6)
.enumerated()
.map { index, interger in
index > 2 ? interger * 2 : interger
}
.subscribe(onNext: {
print($0)
// 1
// 2
// 3
// 8
// 10
// 12
})
.disposed(by: disposeBag)
B. 내부의 Observable 변환하기
struct Student {
var score: BehaviorSubject<Int>
}
- inner observable이라 함은 Observable안에 있는 Observable을 말한다.
- Student는 BehaviorSubject 타입의 score라는 프로퍼티를 갖는 struct다. 이를 Observable로 활용하게 되면 score는 inner observable이 된다
- RxSwift는 flatMap 연산자 내에 몇 가지 연산자를 가지고 있다. 이들은 observable 내부로 들어가서 inner observable 속성들과 작업을 가능하게 한다
1. flatMap
Observable시퀀스의 element당 한 개의 새로운 Observable 시퀀스를 생성한다. 이렇게 생성된 여러개의 새로운 시퀀스를 하나의 시퀀스로 합쳐준다.
-
01엘리먼트가 flatMap을 만나 새로운 시퀀스가 생겼다. 이 시퀀스는 01엘리먼트의 value에 대한 시퀀스다.
새로 생성된 시퀀스에 10 이벤트가 발생했고, 최종 시퀀스(제일 하단 시퀀스)로 전달되었다.
즉, 01엘리먼트의 value에 변동이 있으면, 이 시퀀스에 이벤트가 발생하는 것이다. -
02엘리먼트가 flatMap을 만나 새로운 시퀀스가 생겼다. 마찬가지로, 이 시퀀스는 02엘리먼트의 value에 대한 시퀀스다.
새로 생성된 시퀀스에 20 이벤트가 발생했고, 최종 시퀀스로 전달되었다.
02엘리먼트의 value에 변동이 있으면, 이 시퀀스에 이벤트가 발생한다. 02도 마찬가지다. -
01엘리먼트의 value에 변동이 생겼다. 1에서 4로 바뀐 것이다. (1에서 4로 바뀌는것이 그림에는 표현되지 않았습니다. 그림이 지저분해지니깐)
flatMap이 생성한 시퀀스 중 01엘리먼트에 해당하는 시퀀스에 40이라는 이벤트가 발생하여, 최종 시퀀스로 전달되었다. -
02엘리먼트의 value에 변동이 생겼다. 2에서 5로 바뀐 것이다.
그래서, flatMap이 생성한 02엘리먼트에 해당하는 시퀀스에 50이라는 이벤트가 발생했고, 최종 시퀀스로 전달되었다.
정리해보자면, flatMap은 한 시퀀스의 element를 전달받아 이를 변형한 새로운 시퀀스를 만들고, (Element하나당 Sequence하나를 생성) 이 시퀀스에서 발생하는 모든 이벤트를 최종 시퀀스로 전달하는 것이다.
struct Student {
var score: BehaviorSubject<Int>
}
let disposeBag = DisposeBag()
let ryan = Student(score: BehaviorSubject(value: 80))
let charlotte = Student(score: BehaviorSubject(value: 90))
let student = PublishSubject<Student>()
student
.flatMap{
$0.score
}
.subscribe(onNext: {
print($0)
// 80
// 85
// 90
// 95
// 100
})
.disposed(by: disposeBag)
student.onNext(ryan)
ryan.score.onNext(85)
student.onNext(charlotte)
ryan.score.onNext(95)
charlotte.score.onNext(100)
flatMap은 observable의 element( inner observable )의 변화를 계속 관찰
2. flapMapLatest
- flatMapLatest는 두 개의 Operator인 flatMap과 switchLatest가 합쳐진 역할을 한다
- switchLatest 또한 Observable in observable을 다루는데, 가장 마지막으로 추가된 시퀀스의 inner observable만 넘겨준다.
- 즉, flatMapLatest는 가장 마지막으로 추가된 시퀀스의 inner observable 이벤트만 subscribe하게 된다.
- flatMapLatest가 flatMap과 다른 점은, 자동적으로 이전 observable을 구독해지한다는 것이다.
-
flatMap과 마찬가지로, 01엘리먼트의 value에 해당하는 시퀀스가 생긴다. 01엘리먼트의 최초 value인 1에 10을 곱하여 최종 시퀀스로 전달된다.
-
마찬가지로, 02엘리먼트의 value에 해당하는 시퀀스가 생긴다. 02엘리먼트의 최초 value인 2에 10을 곱한 값이 최종 시퀀스로 전달된다.
그 뒤에, 01엘리먼트의 value가 3으로 바뀌었나보다. 30이 전달된걸 보니...하지만 최종 시퀀스에는 전달되지 않는다. 왜일까??
flatMapLatest는 가장 마지막으로 생성된 시퀀스. 즉 02엘리먼트에 해당하는 시퀀스의 value만 전달하기 때문이다.
(flatMapLatest는 실제로 01엘리먼트의 value에 10을 곱하여 30이라는 값을 만들어내지만, 무시된다.) -
03엘리먼트에 해당하는 시퀀스가 생겼고, 최초 value인 4에 10을 곱해 40이 전달되었다.
그 다음, 02엘리먼트의 value가 5로 바뀌어 50이 전달되었다.
하지만!! 가장 최근에 추가된 시퀀스는 03엘리먼트에 해당하는 시퀀스이기 때문에, 50은 최종 시퀀스에 전달되지 않는다.
그 뒤, 03엘리먼트의 value가 6이되어 60이 전달되었다. 03엘리먼트는 가장 최근에 생성된 시퀀스이기 때문에, 60은 최종 시퀀스로 전달된다.
struct Student {
var score: BehaviorSubject<Int>
}
let disposeBag = DisposeBag()
let ryan = Student(score: BehaviorSubject(value: 80))
let charlotte = Student(score: BehaviorSubject(value: 90))
let student = PublishSubject<Student>()
student
.flatMapLatest {
$0.score
}
.subscribe(onNext: {
print($0)
// 80
// 85
// 90 - ryan에 대한 구독 해지, charlotte으로 전환
// 100
})
.disposed(by: disposeBag)
student.onNext(ryan)
ryan.score.onNext(85)
student.onNext(charlotte)
ryan.score.onNext(95)
charlotte.score.onNext(100)
3. 사용 예시
- flatMapLatest는 네트워킹 조작에서 가장 흔하게 쓰일 수 있다.
- 사전으로 단어를 찾는 것을 생각해보자. 사용자가 각 문자 s, w, i, f, t를 입력하면 새 검색을 실행하고, 이전 검색 결과 (s, sw, swi, swif로 검색한 값)는 무시해야할 때 사용할 수 있을 것
C. 이벤트 관찰하기
- observable을 observable의 이벤트로 변환할 때 사용할 연산자 materialize / dematerialize가 있다.
- 보통 observable 속성을 가진 observable 항목을 제어하는 것이 불가능하고, 외부적으로 observable이 종료되는 것을 방지할 목적으로 error 이벤트를 처리하고 싶을 때 사용할 수 있다.
struct Student {
var score: BehaviorSubject<Int>
}
enum MyError: Error {
case anError
}
let disposeBag = DisposeBag()
let ryan = Student(score: BehaviorSubject(value: 80))
let charlotte = Student(score: BehaviorSubject(value: 100))
let student = BehaviorSubject(value: ryan)
let studentScore = student
.flatMapLatest{
$0.score
// .materialize()
// Observable<Int> → Observable<Event<Int>>으로 방출 타입 변경
}
studentScore
// .filter {
// guard $0.error == nil else {
// print($0.error!)
// return false
// }
// return true
// }
// .dematerialize()
// error이벤트를 필터하여 걸러준다.
// .materialize()로 변경된 Event타입을 원래의 타입(여기선 Int)으로 변경
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
ryan.score.onNext(85)
ryan.score.onError(MyError.anError)
// error 이벤트를 따로 처리해주지 않았기 때문에, student 시퀀스가 종료되고, studentScore 시퀀스도 종료된다.
ryan.score.onNext(90)
student.onNext(charlotte)
- materialize / dematerialize 적용 전
- observable 항목을 제어하는 것이 불가능하고, 외부적으로 error를 통해서 observable이 종료
- print output :
80
85
Unhandled error happened: anError
- materialize / dematerialize 적용 후
- observable 항목을 제어하는 것이 가능하고, 외부적으로 error에도 종료되지 않고 처리할 수 있도록 구성 가능
- print output :
80
85
anError
100
Activity