Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transforming Operator #21

Open
simoniful opened this issue Apr 14, 2022 · 0 comments
Open

Transforming Operator #21

simoniful opened this issue Apr 14, 2022 · 0 comments
Labels

Comments

@simoniful
Copy link
Owner

simoniful commented Apr 14, 2022

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 시퀀스를 생성한다. 이렇게 생성된 여러개의 새로운 시퀀스를 하나의 시퀀스로 합쳐준다.

  1. 01엘리먼트가 flatMap을 만나 새로운 시퀀스가 생겼다. 이 시퀀스는 01엘리먼트의 value에 대한 시퀀스다.
    새로 생성된 시퀀스에 10 이벤트가 발생했고, 최종 시퀀스(제일 하단 시퀀스)로 전달되었다.
    즉, 01엘리먼트의 value에 변동이 있으면, 이 시퀀스에 이벤트가 발생하는 것이다.

  2. 02엘리먼트가 flatMap을 만나 새로운 시퀀스가 생겼다. 마찬가지로, 이 시퀀스는 02엘리먼트의 value에 대한 시퀀스다.
    새로 생성된 시퀀스에 20 이벤트가 발생했고, 최종 시퀀스로 전달되었다.
    02엘리먼트의 value에 변동이 있으면, 이 시퀀스에 이벤트가 발생한다. 02도 마찬가지다.

  3. 01엘리먼트의 value에 변동이 생겼다. 1에서 4로 바뀐 것이다. (1에서 4로 바뀌는것이 그림에는 표현되지 않았습니다. 그림이 지저분해지니깐)
    flatMap이 생성한 시퀀스 중 01엘리먼트에 해당하는 시퀀스에 40이라는 이벤트가 발생하여, 최종 시퀀스로 전달되었다.

  4. 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을 구독해지한다는 것이다.

  1. flatMap과 마찬가지로, 01엘리먼트의 value에 해당하는 시퀀스가 생긴다. 01엘리먼트의 최초 value인 1에 10을 곱하여 최종 시퀀스로 전달된다.

  2. 마찬가지로, 02엘리먼트의 value에 해당하는 시퀀스가 생긴다. 02엘리먼트의 최초 value인 2에 10을 곱한 값이 최종 시퀀스로 전달된다.
    그 뒤에, 01엘리먼트의 value가 3으로 바뀌었나보다. 30이 전달된걸 보니...하지만 최종 시퀀스에는 전달되지 않는다. 왜일까??
    flatMapLatest는 가장 마지막으로 생성된 시퀀스. 즉 02엘리먼트에 해당하는 시퀀스의 value만 전달하기 때문이다.
    (flatMapLatest는 실제로 01엘리먼트의 value에 10을 곱하여 30이라는 값을 만들어내지만, 무시된다.)

  3. 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
@simoniful simoniful added the Rx label Apr 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant