진행하고 있는 프로젝트에서 검색 결과에 대한 pagination기능을 구현할 때 스크롤이 일정 범위에 도달하면 다음 페이지 결과를 가져오기 위해서 네트워크 요청을 보낼때
연속적인 스크롤 오프셋 이벤트들 중 하나의 이벤트에 대해서만 반응하여 네트워크 요청을 보내기 위해서 flatMapLatest를 사용했다.
아래 코드 예시를 보면,
스크롤 오프셋 이벤트를 방출하는 loadMoreContent Observable에
private func collectionViewContentOffsetChanged() -> Observable<Void> {
return collectionView.rx.contentOffset
.withUnretained(self)
.filter { (self, offset) in
guard self.collectionView.contentSize.height != 0 else {
return false
}
return self.collectionView.frame.height + offset.y + 100 >= self.collectionView.contentSize.height
}
.map { _ in }
}
let input = SearchViewModel.Input(
//..
loadMoreContent: collectionViewContentOffsetChanged()
)
flatMapLatest를 이용해서 가장 마지막 이벤트에 대해서만 네트워크 요청을 보내도록 처리하였다.
let moreResults = input.loadMoreContent
.map { _ in
print("event 발생!")
}
.flatMapLatest { _ -> Observable<[SearchCellViewModel]> in
return self.useCase.getSearchResults(with: self.searchText, page: self.page)
.map { (movieList) -> [SearchCellViewModel] in
print("네트워크 요청")
self.page = movieList.page + 1
return movieList.items.filter { $0.posterPath != "" }
.map { SearchCellViewModel(movie: $0) }
}
}
이렇게 하면 수많은 이벤트가 발생하지만, 네트워크 요청은 딱 한번만 이루어지는 모습을 볼 수 있다.
이 과정에서 flatMap, flatMapFirst, flatMapLatest의 차이에 대해 알아보게 되었다,,
똑같은 예시에 대해서 Operator만 flatMap, flatMapFirst, flatMapLatest로 바꿔가며 결과가 어떻게 다르게 나오는지 살펴보자.
flatMap은 한 Observable에서 발생한 이벤트를 다른 Observable로 변환하는 Operator이다.
만약 특정 이벤트로 만들어진 Observable 시퀀스가 진행되고 있는 도중에 다른 이벤트가 발생하면 그 이벤트로 만들어진 Observable 시퀀스도 동시에 진행할 수 있다.
이렇게만 말하면 이해가 안갈테니 예시를 보자.
아래 예시는 1, 2, 3 이벤트를 방출하는 Observable에 대해서 각각의 이벤트를 sequenceString Observable로 변환한다.
let sequenceInt = Observable.of(1, 2, 3)
let sequenceString = Observable.of("A", "B", "C", "D")
sequenceInt
.flatMap { (x: Int) -> Observable<String> in
print("Emit Int Item : \(x)")
return sequenceString
}
.subscribe(onNext: {
print("Emit String Item : \($0)")
})
.disposed(by: disposeBag)
출력 결과를 보면, 이벤트 1에 대한 Observable 시퀀스가 진행되고 있는 와중에 새로운 이벤트 2가 발생하면 2에 대한 Observable 시퀀스도 동시에 발생하고, 마찬가지로 새로운 이벤트 3이 발생하면 3에 대한 Observable 시퀀스도 시작되어 각각의 Observable 이벤트들의 순서가 섞여서 나오는 모습을 볼 수 있다.
1에 대한 Observable 시퀀스 A->B->C->D 중간에
2에 대한 Observable 시퀀스 A->B->C->D가 시작하고 나서,
3에 대한 Observable 시퀀스 A->B->C->D도 시작한다.
그렇다면 flapMapFirst는 뭘까
Observable의 이벤트에 대한 Observable 시퀀스가 진행되고 있는 도중에 다음 이벤트가 발생하더라도, 원래 진행되고 있던 시퀀스가 종료될때까지 다음 이벤트에 대한 Observable 시퀀스를 생성하지 않는다.
그리고 원래 진행되던 이벤트의 Observable 시퀀스가 종료되고 나서 발생한 이벤트에 대해서는 다시 Observable 시퀀스를 생성한다.
let sequenceInt = Observable.of(1, 2, 3)
let sequenceString = Observable.of("A", "B", "C", "D")
sequenceInt
.flatMapFirst { (x: Int) -> Observable<String> in
print("Emit Int Item : \(x)")
return sequenceString
}
.subscribe(onNext: {
print("Emit String Item : \($0)")
})
.disposed(by: disposeBag)
따라서 이 예시에서는 이벤트 1에 대한 Observable 시퀀스 A->B->C->D가 진행되고 있는 와중에 이벤트 2, 3이 발생했기 때문에 무시된다.
마지막으로 flatMapLatest는 특정 이벤트에 대한 Observable 시퀀스가 진행되고 있는 동안에 다음 이벤트가 발생하면 Observable을 dispose 시키고, 다음 이벤트에 대한 Observable을 생성하게 된다.
let sequenceInt = Observable.of(1, 2, 3)
let sequenceString = Observable.of("A", "B", "C", "D")
sequenceInt
.flatMapLatest { (x: Int) -> Observable<String> in
print("Emit Int Item : \(x)")
return sequenceString
}
.subscribe(onNext: {
print("Emit String Item : \($0)")
})
.disposed(by: disposeBag)
이벤트 1에 대한 Observable 시퀀스가 A -> 까지 진행된 순간에 다음 이벤트 2가 발생하면 이벤트 1에 대한 Observable은 dispose되고, 이벤트 2에 대한 Observable 시퀀스가 시작된다.
마찬가지로 이벤트 2에 대한 Observable 시퀀스가 A -> 까지 진행된 순간에 다음 이벤트 3이 발생하면 이벤트 2에 대한 Observable은 dispose되고 이벤트 3에 대한 Observable 시퀀스가 시작된다.
이벤트 3이 가장 마지막 이벤트이기 때문에 이벤트 3에 대한 Observable 시퀀스는 A->B->C->D 끝까지 진행이 된다.
https://reactivex.io/documentation/operators/flatmap.html
http://minsone.github.io/programming/reactive-swift-flatmap-flatmapfirst-flatmaplatest