Rxswift operators - flatmap, flatmapLatest, merge
참조 : http://adamborek.com/thinking-rxswift/
Flatmap
검색결과에 따라 API 호출후
노래 리스트를 응답 받아 그려야하는 상황이다.
Observable<String>
-> Observable<[Track]>
-> Observable<[TrackRenderable]>:
그러면 이런 흐름으로 map operator를 사용하여Observable을 전달받아 처리하면 된다.
그러나 검색어에 대한 bservable<String> 은 동기적이고
Observable<[Track]> 은 API응답이므로 비동기적이다.
flatmap을 사용해 해결할수있다.
searchBar.rx.text.orEmpty.flatmap { query in
return API.fetch(query:query) // Observable<Resutl> 리턴 해야함
}
Observable 을 응답값으로 받는 방법은 여기나와있다
http://adamborek.com/practical-introduction-rxswift/
FlatmapLatest
응답 딜레이에 따른 에러
- let 을 입력 → let 노래 리스트 요청
- let it go 입력 → let it go 노래 리스트 요청
- let it go 노래 리스트 응답
- let 노래 리스트 응답
이런경우 원하는 결과 값은 let it go 리스트 이지만 let 리스트가 결과적으로 받아오게 된다.
flatmaplatest를 사용하면 이전 구독을 모두 취소한다. 따라서 새 데이터가 오래된 응답에의해 대체될 수가 없게된다.
searchBar.rx.text.orEmpty.flatmapLatest { query in
return API.fetch(query:query) // Observable<Resutl> 리턴 해야함
}.map { tracks in
return tracks.map(TrackRenderable.init)
} //Observable<[TrackRenderable]>
merge
만약 텍스트가 입력될때는 기존 리스트를 지우고 다시 받아오려한다면 어떻게 해야할까
기존에는 검색을 하면서 리스트가 수정 되겠지만 지우는 기능은 없다.
비어있는 노래 리스트를 리턴하는 Observable을 만들어
merge를 통해 합쳐주면 된다
//텍스트가 수정될때
let searchTextChanged = searchBar.rx.text.orEmpty.asObservable().skip(1)
//기존 검색 로직
let tracksFromSpotify = searchTextChanged
.flatMapLatest { query in
return API.fetch(query:query)
}.map { tracks in
return tracks.map(TrackRenderable.init)
}
//지우는 로직
let clearTracksOnQueryChanged = searchTextChanged
.map { _ in return [TrackRenderable]() }
// 병합
let tracks = Observable.of(tracksFromSpotify, clearTracksOnQueryChanged).merge()
만약 Pullto Refresh를 여기다 구현한다 해보자.
refresh를 요청하면 마지막 searchtext를 가지고 쿼리를 해야한다.
let refreshControl = UIRefreshControl()
tableView.addSubview(refreshControl)
let didPullToRefresh: Observable<Void> = refreshControl.rx.controlEvent(.valueChanged)
.map { [refreshControl] in
return refreshControl.isRefreshing
}.filter { $0 == true }
.map { _ in return () }
//refresh 끝나면 refreshControl.endRefreshing()
테이블뷰에 refresh control 을 넣어서 쉽게 사용할수있다.
기존에는 searchText가 바뀌면 쿼리했지만 이제는 merge를 통해 refresh 호출시에도 쿼리할것이다
여기서 Observable<String> (searchTextChanged) 의 마지막 값을 가져와야하는데
let refreshLastQuery = didPullToRefresh
.withLatestFrom(searchTextChanged)
//didPullToRefresh: Observable<Void>
//searchTextChanged: Observable<String>
이렇게 사용할수 있다.
merge를 사용하면
let tracksFromSpotify = Observable.of(searchTextChanged, refreshLastQuery).merge()
.flatMapLatest { query in
return API.fetch(query:query)
}.map { tracks in
return tracks.map(TrackRenderable.init)
}.do(onNext: { [refreshControl] _ in
refreshControl.endRefreshing()
})
이렇게 사용 가능하다.