buffer, flatMap, map, scan, window
* 이 포스트는 RxSwift 4.3.1, swift 4.2 버전을 기준으로 작성되었습니다.
Observable 의 이벤트를 타입을 변경하거나, 새로운 모델로 이벤트를 가공하거나 등, 이벤트를 변형하고자 할때 사용하는 메서드들에 대해 알아보자.
RxSwift 에서 이와 관련된 메서드들은 buffer, flatMap, map, scan, window, groupBy 이다.
이벤트를 버퍼에 저장한뒤 묶어서 방출한다.
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 를 조절하면서 테스트 해보면 어떻게 사용되는지 알 수 있다.
개인적인 의견 : 동적으로 이벤트를 생성하는 코드가 있고, 성능이나, 기타 이유에 의해 어느정도 이벤트들을 묶어서 처리해야 하는 경우 사용하면 좋을 것 같다.
( 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)
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))
원본 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
원본 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)
(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)
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)
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)
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
기본값을 가지고, 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>
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
이벤트들을 분류해서 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 의 변형을 통해, 발생하는 이벤트들을 상황에 따라 유연하게 처리가 가능하다.