brunch

매거진 ReactiveX

You can make anything
by writing

C.S.Lewis

by Tilltue Jul 29. 2016

RxSwift, Observable 의 변형

buffer, flatMap, map, scan, window

* 이 포스트는 RxSwift 4.3.1, swift 4.2 버전을 기준으로 작성되었습니다.


Transforming Observables

Observable 의 이벤트를 타입을 변경하거나, 새로운 모델로 이벤트를 가공하거나 등, 이벤트를 변형하고자 할때 사용하는 메서드들에 대해 알아보자. 

RxSwift 에서 이와 관련된 메서드들은 buffer, flatMap, map, scan, window, groupBy 이다.


1. buffer

이벤트를 버퍼에 저장한뒤 묶어서 방출한다.

timeSpan : 버퍼에 저장되는 시간간격

count : 버퍼에 저장되는 최대 이벤트 갯수


함수 원형

func buffer(timeSpan: RxTimeInterval, 

                    count: Int, 

                    scheduler: SchedulerType)

        -> Observable<[E]>



예제

            let bufferTest = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)

            bufferTest.buffer(timeSpan: 3, count: 3, scheduler: MainScheduler.instance)

                .subscribe{ event in

                    print(event)

                }.disposed(by: disposeBag)


결과

next([0, 1])

next([2, 3, 4])

next([5, 6, 7])


최초 3초 동안 발생한 이벤트는 2개 이므로 2개가 묶여져서 observer 에게 전달되고, 이후에 3개씩 이벤트가 묶여져서 전달된다.

시간값과 count 를 조절하면서 테스트 해보면 어떻게 사용되는지 알 수 있다.


개인적인 의견 : 동적으로 이벤트를 생성하는 코드가 있고, 성능이나, 기타 이유에 의해 어느정도 이벤트들을 묶어서 처리해야 하는 경우 사용하면 좋을 것 같다.


2. flatMap

( flatMapWithIndex,flatMapFirst,flatMapLatest )

원본 Observable의 이벤트를 받아 새로운 Observable 로 변형한다.

예제

            let timer = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)

            let flatMapTest = timer.flatMap { (num) -> Observable<String> in

                return Observable<String>.create{ observer in

                    observer.on(.next("AA\(num)"))

                    observer.on(.next("AA\(num)"))

                    return Disposables.create {

                        print("dispose")

                    }

                }

            }

            flatMapTest.subscribe{ event in

                print(event)

            }.disposed(by: disposeBag)


결과

next(AA0)

next(BB0)

next(AA1)

next(BB1)

next(AA2)

next(BB2)

next(AA3)

next(BB3)



 flatMapWithIndex

deprecate 되었다. enumerate().flatMap 을 사용하면 된다.

이벤트와 인덱스를 같이 받을수 있다.


예제

            let timer = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)

            let flatMapTest = timer.flatMapWithIndex{ (num, index) -> Observable<String> in

                return Observable<String>.create{ observer in

                    observer.on(.next("AA\(index,num)"))

                    observer.on(.next("BB\(index,num)"))

                    return Disposables.create {

                        print("dispose")

                    }

                }

            }

            flatMapTest.subscribe{ event in

                print(event)

            }.addDisposableTo(disposeBag)



결과

next(AA(0, 0))

next(BB(0, 0))

next(AA(1, 1))

next(BB(1, 1))

next(AA(2, 2))

next(BB(2, 2))


- flatMapFirst

원본 Observable의 이벤트를 받아 새로운 Observable 로 변형하지만, 이 Observable 이 완료되기 전에 이전 Observable 의 다른 이벤트들은 무시하게 된다.

예제

            let timer = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)

            let flatMapTest = timer.flatMapFirst { (num) -> Observable<String> in

                return Observable<String>.create{ observer in

                    observer.on(.next("AA\(num)"))

                    observer.on(.next("BB\(num)"))

                    observer.onCompleted()

                    return Disposables.create {

                        print("dispose")

                    }

                }.delaySubscription(3, scheduler: MainScheduler.instance)

            }

            flatMapTest.subscribe{ event in

                print(event)

            }.disposed(by: disposeBag)



flatmap 으로 이벤트를 변환한 Observable 은 subscription 에 3초의 딜레이를 줬다.

3초간 발생한 원본 observable 의 이벤트들이 무시된것을 확인 할 수 있다.


결과

next(AA0)

next(BB0)

dispose

next(AA4)

next(BB4)

dispose


- flatMapLatest

원본 Observable의 이벤트를 받아 새로운 Observable 로 변형하지만, 다음 원본 이벤트가 발생하면 dispose 되고 다시 변형된 Observable 을 생성한다. subscription에 dispose 가 전달 되지는 않는다.


예제

            let timer = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)

            let flatMapTest = timer.flatMapLatest { (num) -> Observable<Int> in

                return Observable<Int>.interval(0.3, scheduler: MainScheduler.instance)

            }

            flatMapTest.subscribe{ event in

                print(event)

            }.disposed(by: disposeBag)


1초씩 발생하는 원본 Observable  의 다음 이벤트가 발생하기 전까지 flatMap 으로 변환된 0.3초당 한번으로 변형된 이벤트가 전달되고, 이후 이벤트가 전달되었을때 다시 0.3초당 한번 이벤트를 발생하는 옵저버블이 수행된다.


결과

next(0)

next(1)

next(2)

---- 1초 

next(0)

next(1)

next(2)

---- 2초 

next(0)


3. map

http://rxmarbles.com/#map

(mapWithIndex)
이벤트를 다른 이벤트로 변환한다.

 예제

            let timer = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)

            let mapTest = timer.map{ "map test\($0)" }

            mapTest.subscribe{ event in

                print(event)

            }.disposed(by: disposeBag)


결과

next(map test0)

next(map test1)

next(map test2)


mapWithIndex

deprecate 되었다. enumerate().map 을 사용하면 된다.

map 과 동일하지만 아이템과 인덱스를 같이 받을수 있다.


예제

            let timer = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)

            let mapTest = timer.mapWithIndex{ (num, index) -> String in

                return "map test\(num) index:\(index)"

            }

            mapTest.subscribe{ event in

                print(event)

            }.disposed(by: disposeBag)

결과

next(map test0 index:0)

next(map test1 index:1)

next(map test2 index:2)

next(map test3 index:3)


4. scan

http://rxmarbles.com/#scan

scan 은 값을 축적해서(혹은 변경 없이 저장) 가지고 있을 수 있으며, 이 값을 통해 이벤트를 변형할 수 있는 메서드이다.

함수 원형

func scan<A>(into seed: A, 

                        accumulator: @escaping (inout A, E) throws -> ())

        -> Observable<A>

변형하는 이벤트의 타입은 원본 이벤트 타입과 같아야 함을 알수있다.


예제

            let timer = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)

            let scanTest = timer.scan(10) { (accumulator, num) -> Int in

                return accumulator + num

            }

            scanTest.subscribe{ event in

                print(event)

            }.disposed(by: disposeBag)


초기값 10을 가지도록 했고 이벤트가 발생하면 그 값을 더해 축적시키도록 변형했다.


결과

next(10)

next(11)

next(13)

next(16)

next(20)


4. window

buffer 와 유사하지만 모여진 이벤트로 새로운 observable  을 생성한다는 것이 다르다.


함수 원형

func window(timeSpan: RxTimeInterval, 

                        count: Int, 

                        scheduler: SchedulerType)

                        -> Observable<Observable<E>>

예제

           let rangeObservable = Observable<Int>.range(start: 0, count: 10)

            let windowTest = rangeObservable.window(timeSpan: 1000, count: 3, scheduler: MainScheduler.instance)

            windowTest.subscribe(onNext: { [unowned self] observable in

                observable.subscribe{ event in

                    print(event)

                }.disposed(by: disposeBag)

            }).disposed(by: disposeBag)


원본 이벤트 를 최대 3개씩 묶어 새로운 observable 을 이벤트로 전달하도록 변형되었다.


결과

next(0)

next(1)

next(2)

completed

next(3)

next(4)

next(5)

completed

next(6)

next(7)

next(8)

completed

next(9)

completed


5. reduce

기본값을 가지고, emit 된 값들을 연산해서 하나의 결과값 이벤트를 발생하는 Observable 로 변형한다.


두가지 함수 원형

func reduce<A, R>(_ seed: A, 

                                    accumulator: @escaping (A, E) throws -> A, 

                                    mapResult: @escaping (A) throws -> R)

                                    -> Observable<R>

func reduce<A>(_ seed: A, 

                            accumulator: @escaping (A, E) throws -> A)

                            -> Observable<A>

This sort of operation is sometimes called “accumulate,” “aggregate,” “compress,” “fold,” “inject”


seed 값을 기본으로 연산자를 받아 결과값을 만들어 낸다.

예제

            let rangeObservable = Observable<Int>.range(start: 0, count: 10)

            let reduceTest = rangeObservable.reduce(0, accumulator: +)

            reduceTest.subscribe{ event in

                print(event)

            }.disposed(by: disposeBag)


결과

next(45)

completed


6. groupBy


이벤트들을 분류해서 key 값을통해 다른 Observable 로 변형할수 있다.

keySelector 에서 각 요소들에서 키값을 추출하는 함수를 전달한다.

groupby 는 이 키 값과 본래 값을 같이 가진 Observable 로 변형시켜준다.


함수 원형

func groupBy<K: Hashable>(keySelector: @escaping (E) throws -> K)

        -> Observable<GroupedObservable<K,E>>



예제

let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)

timer.groupBy{ $0 % 2 == 0 }.flatMap{ grouped -> Observable<String> in

            if grouped.key {

                return grouped.map{ "even \($0)" }

            }else {

                return grouped.map{ "odd \($0)" }

            }

        }.debug().subscribe().disposed(by: disposeBag)


결과

(groupByTest()) -> subscribed

(groupByTest()) -> Event next(even 0)

(groupByTest()) -> Event next(odd 1)

(groupByTest()) -> Event next(even 2)

(groupByTest()) -> Event next(odd 3)


홀수와 짝수를 구분하도록 한 예제이다.

각 이벤트를 구별할수 있고 그 이벤트들로 별도의 Observable 을 만들어야 할때 유리하다.



Observable 의 변형을 통해, 발생하는 이벤트들을 상황에 따라 유연하게 처리가 가능하다.

매거진의 이전글 RxSwift, Error 처리
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari