본문 바로가기

RxSwift

RxSwift Playground 파헤치기2 - Operator (feat. startWith) 구조 파악하기

안녕하세요. iOS 개발자 JHM입니다.

저번 주에는 RxSwift의 콘셉트와 구조를 다루는 PlaygroundIntroduction을 파악해보았습니다.

Introduction 이후에는 대부분의 내용들이 Observable을 다른 형태의 Observable로 변형시켜주는 Operator를 설명하는 내용으로 구성됩니다.

 

글 구성 목록표

 

Operator를 설명하는 챕터들은 단순히 Operator를 설명하고, 예시를 보여주는 정도입니다. 이러한 구성을 접하다 보니, Opeartor는 저에게 암기의 대상이었습니다. 암기를 해서 사용하다 보니 종종 실제 적용 시 잘못 사용하는 경우가 빈번했습니다.

 

Operator 예시

 

그래서 이번에는 Opeartor 코드를 열어보면서 이해를 해보았고, 그 안에서 Operator가 구성되는 구현 패턴을 파악했습니다. 그 특정 구현을 이해하고 Operator를 Code Level로 파악하게 되면서 Opeartor에 대한 이해도가 올라갔습니다. 오늘은 제가 파악한 이 패턴에 대해서 startWith Operator를 예시를 들며 전달해보고자 합니다.

startWith

1. startWith 해석

- Emits the specified sequence of elements before beginning to emit the elements from the source Observable.

- 소스 Observable에서 요소 방출을 시작하기 전에 지정된 요소 시퀀스를 방출합니다.

: startWith의 Parameter로 전달된 요소가 본래 Observable의 동작 전에 먼저 방출되는 Operator이며, 고정된 값을 먼저 방출해야 하는 경우 사용됩니다.

startWith Example

example("startWith") {
    let disposeBag = DisposeBag()
    
    Observable.of("🐶", "🐱", "🐭", "🐹")
        .startWith("1️⃣")
        .startWith("2️⃣")
        .startWith("3️⃣", "🅰️", "🅱️")
        .subscribe(onNext: { print($0) })
        .disposed(by: disposeBag)
}

--- startWith example ---
"3️⃣"
"🅰️"
"🅱️"
"2️⃣"
"1️⃣"
"🐶"
"🐱"
"🐭"
"🐹"

startWith Operator가 왜 저렇게 동작하는지 파악한 이후 패턴을 말씀드리겠습니다.

2. startWith 분석 공유

startWith Operator를 control + command + 클릭을 하면, startWith와 관련한 Definition 리스트가 노출됩니다.

 

startWith Definition

 

해당 리스트 중에서 ObservableType.startWith를 선택합니다. 선택하게 되면 아래와 같은 코드가 보이게 됩니다.

extension ObservableType {

    /**
     Prepends a sequence of values to an observable sequence.

     - seealso: [startWith operator on reactivex.io](http://reactivex.io/documentation/operators/startwith.html)

     - parameter elements: Elements to prepend to the specified sequence.
     - returns: The source sequence prepended with the specified values.
     */
    public func startWith(_ elements: Element ...)
        -> Observable<Element> {
            return StartWith(source: self.asObservable(), elements: elements)
    }
}

코드를 보면 전달받은 elements라는 Parameter가 self.asObservable [Operator를 호출한 Observable 해당 글에서는 CODE Example의 Observable.of("🐶", "🐱", "🐭", "🐹")입니다]를 StartWith로 전달한 후 반환(return)해주고 있습니다. 이제 StartWith를 분석해보겠습니다.

final private class StartWith<Element>: Producer<Element> {
    let elements: [Element]
    let source: Observable<Element>

    init(source: Observable<Element>, elements: [Element]) {
        self.source = source
        self.elements = elements
        super.init()
    }

    override func run<Observer: ObserverType>(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element {
        for e in self.elements {
            observer.on(.next(e))
        }

        return (sink: Disposables.create(), subscription: self.source.subscribe(observer))
    }
}

- StartWith는 Producer라는 Class를 상속받고 있습니다.

- StartWith는 run이라는 함수의 구현을 책임지고 있고, run은 내부의 for 문을 통해 전달받은 elements를 next Event로 변경하고 observer에게 전달해주고 있습니다. 고로 Operator의 핵심을 보려 할 때는, Producer의 역할과 run이라는 함수의 역할을 파악해야 합니다.

3. RxSwift의 Operator 구현 방식에 대한 공유

Operator는 위에서 찾은 것과 같이 Producer라는 Class를 상속받고 run이라는 함수를 통해 Operator의 역할을 구현하고 있습니다. 이 부분은 Producer Class를 살펴보면 확실하게 이해할 수 있습니다.

class Producer<Element>: Observable<Element> {
    override init() {
        super.init()
    }

    override func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
        if !CurrentThreadScheduler.isScheduleRequired {
            // The returned disposable needs to release all references once it was disposed.
            let disposer = SinkDisposer()
            let sinkAndSubscription = self.run(observer, cancel: disposer)
            disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription)

            return disposer
        }
        else {
            return CurrentThreadScheduler.instance.schedule(()) { _ in
                let disposer = SinkDisposer()
                let sinkAndSubscription = self.run(observer, cancel: disposer)
                disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription)

                return disposer
            }
        }
    }

    func run<Observer: ObserverType>(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element {
        rxAbstractMethod()
    }
}

Producer는 Observable이 Event를 발생시키도록 하는 subscribe 함수에 대한 구현을 책임지고 있습니다. 그리고 run을 보면 rxAbstractMethod()만 존재합니다. rxAbstractMethod()의 역할은 추상화 메서드 표현하는 것이며, 자신을 상속받는 대상이 구현의 책임을 가지도록 합니다. 또한, run은 subscribe 내부에서 꼭 실행되는 것을 볼 수 있습니다.

따라서, "Operator들은 Producer를 상속받아 구현된 Observable"이라는 정도의 이해만 가지고 run 함수를 분석하시면 됩니다.

이러한 패턴을 파악한 이후 다른 Opeartor들의 구현도 살펴보았을 때, 큰 틀은 같지만, 이와 조금 다른 패턴을 보이는 Operator들이 존재했습니다. 다음 글에서는 조금 다른 패턴을 보이는 Operator를 분석해보겠습니다.

'RxSwift' 카테고리의 다른 글

RxSwift Playground 파헤치기1 - Introduction  (0) 2021.03.28
RxSwift 사용기  (0) 2019.08.30