본문 바로가기
iOS/RxSwift

Ch7. Transforming Operators

by 헤콩 2023. 2. 24.
반응형

본 게시물은 Florent Pillet, Junior Bontognali, Marin Todorov, Scott Gardner - RxSwift.  Reactive Programming with Swift (2017, Razeware LLC) 책과 ReactiveX 사이트를 기반으로 작성되었습니다.

 

RxSwift를 공부하는 데에 이 책을 읽으면 좋겠다고 생각하였고, 단지 읽기만 하는 것보다 한국어로 직접 정리해놓으면 더 기억하기 좋을 것 같아 게시물을 작성하게 되었습니다.

이번 게시물에서는 마블 다이어그램과, 마블 다이어그램에 해당하는 메서드 예시를 중점으로 정리하였습니다.

 


이번 게시물에서는 RxSwift에서 가장 중요한 연산자라고 할 수 있는 Transforming Operators 에 대해서 배워보겠습니다. Transforming Operators 는 subscriber를 통해 Observable에서 데이터를 준비하는 과정을 포함하여 모든 상황에서 쓰일 수 있을 것입니다. 이전 게시물에서 살펴보았던 filter처럼 이번에도 map(_:) 이나 flatMap(_:) 같이 Swift 표준 라이브러리와 RxSwift 사이에 유사점이 있는 연산자들을 확인할 수 있습니다.

 

👉toArray, map, enumerated, flatMap, flatMapLatest, materialize, dematerialize

 

A. Transforming elements

I. toArray

Observable은 대게 독립적으로 element들을 방출합니다. 하지만 이 Observable을 TableView 또는 CollectionView와 바인딩하는 것처럼 element들을 묶어서 사용하고 싶을 수 있는데, 이때 toArray를 사용할 수 있습니다. 위에 있는 마블 다이어그램을 보면 toArray는 Observable Sequence의 element들을 array의 element들로 넣습니다. 그리고 이렇게 만들어진 array를 next 이벤트를 통해 subscriber에게 방출하게 됩니다.

let disposeBag = DisposeBag()

Observable.of(1, 2, 3)
    .toArray()
    .subscribe(
        onNext: {
            print($0)
        }
    )
    .disposed(by: disposeBag)

 

[ 출력 ]

[1, 2, 3]

 

 

II. map

RxSwift의 map 연산자는 Swift 표준 라이브러리의 map과 같습니다. 단지 Observable에서 동작한다는 점만 다를 뿐이지요. 위의 마블 다이어그램 예시를 보면 map은 각각의 요소에서 2를 곱하는 클로저를 갖고 있습니다. 그럼 아래 코드로 다시 한 번 살펴보겠습니다.

let disposeBag = DisposeBag()

Observable.of(1, 2, 3)
    .map {
        $0 * 2
    }
    .subscribe(
        onNext: {
            print($0)
        }
    )
    .disposed(by: disposeBag)

 

[ 출력 ]

2

4

6

 

 

III. enumerated

RxSwift First Edition에 나오는 ~WithIndex라고 되어있는 메서드들은 다 deprecated 되고 enumerated를 사용하는 것으로 바뀐 것 같습니다. 이전 게시물에서 사용했던 것처럼 여기서도 enumerated를 사용하여 기존의 mapWithIndex 기능을 구현하는 것을 살펴보겠습니다. 아래 코드를 보면 enumerated를 사용해서 element를 해당 index과 value의 튜플값으로 만들고 map을 사용하여 index가 2보다 크면 2배 값을, 그렇지 않다면 본래의 값을 방출하도록 만들어주었습니다.

let disposeBag = DisposeBag()

Observable.of(1,2,3,4,5)
    .enumerated()
    .map { index, value in
        index > 2 ? value * 2 : value
    }
    .subscribe(
        onNext: {
            print($0)
        }
    )
    .disposed(by: disposeBag)

 

[ 출력 ]

1

2

3

8

10

 

 

B. Transforming inner observables

그렇다면 만약 Observable 속성을 갖는 Observable은 어떻게 사용할 수 있을까요?

 

아래 코드를 추가해서 예시에 사용해보겠습니다.

struct Student {
    var score: BehaviorSubject<Int>
}

 

아래에서 배울 flatMap은 우리가 Observable 내부로 들어가 Observable의 속성들과 작업할 수 있도록 해줍니다. 여기서 배울 개념은 상당히 어렵고 복잡하게 느껴질 수 있지만, 하나씩 차근차근 들여다본다면 이후 사용하는데 큰 도움이 될 것입니다.

 

 

I. flatMap

flatMap은 각각의 Observable Sequence 요소를 하나의 Observable로 따로 작업을 하게 하고, 이후 최종적인 Observable Sequence들을 하나의 Observable Sequence에 합치게 됩니다. 음.... 이게 마블 다이어그램 보면서 영어 문장 그대로 받아들이면 이해가 아주 잘 가거든요? 한국어로 번역하려니 마땅한 한국어 단어들을 조합하는게 더 어려워요.. 그래서 일단 영어 문장 그대로 가져왔으니 이 문장은 되도록이면 그냥 영어 그대로 받아들여보시길..ㅜㅜ 

 

"Projects each element of an observable sequence to an observable sequence

and merges the resulting observable sequences into one observable sequence."

 

마블 다이어그램을 보면, 첫 번째 Observable Sequence가 마지막에 있는 구독자에게 가는 Observable Sequence가 되기까지의 과정을 보여줍니다. 처음에 각 Observable들은 O1.value  = 1, O2.value = 2, O3.value = 3 입니다. O1부터 시작해서 flatMap은 객체를 수신하고 value 프로퍼티에 접근해서 10을 곱합니다. 그리고 각각의 element를 가진 Observable Sequence를 가지게 되고, 마지막에 결과적으로 하나의 Observable Sequence로 합쳐서 내보내게 됩니다.

let disposeBag = DisposeBag()

let student1 = Student(score: BehaviorSubject(1))
let student2 = Student(score: BehaviorSubject(2))
let student3 = Student(score: BehaviorSubject(3))

let students = PublishSubject<Student>()

students.asObservable()
    .flatMap { item in
        item.score.map { $0 * 10 }
    }
    .subscribe(
        onNext: {
            print($0)
        }
    )
    .disposed(by: disposeBag)
    
students.onNext(student1)
students.onNext(student2)
students.onNext(student3)

student1.score.onNext(4)
student2.score.onNext(5)

 

[ 출력 ]

10

20

30

40

50

 

 

II. flatMapLatest

위에서 보았던 flatMap에서 가장 최신의 값만을 확인하고 싶은 상황이 있을 수 있습니다. 이럴 때 사용하는 것이 flatMapLatest입니다. 가장 최근의 Observable에서 값을 생성하고 이전 Observable을 구독 해제합니다. flatMapLatest는 네트워킹 조작에서 가장 흔하게 사용될 수 있는데, 사전으로 단어를 찾은 예시를 생각해보면 사용자가 각 문자 a, p, p, l, e 을 입력하면 새 검색을 실행하고, 이전 검색 결과(a, ap, app, appl)는 무시해야할 때 사용할 수 있을 것입니다.

let disposeBag = DisposeBag()

let student1 = Student(score: BehaviorSubject(1))
let student2 = Student(score: BehaviorSubject(2))
let student3 = Student(score: BehaviorSubject(4))

let students = PublishSubject<Student>()

students.asObservable()
    .flatMapLatest { item in
        item.score.map { $0 * 10 }
    }
    .subscribe(
        onNext: {
            print($0)
        }
    )
    .disposed(by: disposeBag)
    
students.onNext(student1)
students.onNext(student2)

student1.score.onNext(3)

students.onNext(student3)

student2.score.onNext(5)
student3.score.onNext(6)

 

[ 출력 ]

10

20

40

60

 

 

C. Observing the events

가끔 Observable을 Observable의 이벤트로 변환하거나 그 변환한것을 다시 되돌려야 할 때가 있는데, 이는 materializedematerialize로 해결할 수 있다.

 

materialize


dematerialize

 

 

 

 

반응형

'iOS > RxSwift' 카테고리의 다른 글

Ch5. Filtering Operators  (0) 2023.02.24
RxSwift. Traits가 뭘까? (Single, Maybe, Completable)  (0) 2023.02.24
Ch3. Subjects  (0) 2023.02.24
Ch2. Observable  (0) 2023.02.24
Ch9. Combining Operators  (1) 2021.01.13

댓글