Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@ha1f
Last active December 8, 2019 12:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ha1f/664cbe644e7238d033291bf47d512c6b to your computer and use it in GitHub Desktop.
Save ha1f/664cbe644e7238d033291bf47d512c6b to your computer and use it in GitHub Desktop.
//
// CachableRequest.swift
// LIPS
//
import Foundation
import RxSwift
import RxCocoa
private extension ObservableType {
static func from<E: Error>(result: Swift.Result<Element, E>) -> Observable<Element> {
switch result {
case let .success(value):
return .just(value)
case let .failure(error):
return .error(error)
}
}
}
private extension Swift.Result {
var value: Success? {
switch self {
case let .success(value):
return value
case .failure:
return nil
}
}
}
/// 結果をキャッシュする
/// 読込中には重複してリクエストを送らない
/// stateとcacheを混ぜたストリームを作らないこと(どっちが先に更新されるかは問わない)
final class CachableRequest<T> {
enum State {
/// 読み込み前
case possible
/// 読込中
case loading
/// 直近のリクエストがエラー
case error(Error)
/// 直近のリクエストが成功
case loaded(T)
}
/// 状態
let state = BehaviorRelay<State>(value: .possible)
/// キャッシュされた値
let cache = BehaviorRelay<T?>(value: nil)
/// リクエストの結果
private let _publisher = PublishRelay<Swift.Result<T, Error>>()
private let isLoadingFlag = IsLoadingFlag()
private let _disposeBag = DisposeBag()
private let _request: Single<T>
init(_ request: Single<T>) {
_request = request
// 直前の結果によってstateを更新
let latestRequestResult = _publisher
.map { result -> State in
switch result {
case let .success(value):
return .loaded(value)
case let .failure(error):
return .error(error)
}
}
Observable
.combineLatest(latestRequestResult, isLoadingFlag.value)
.map { combined -> State in
if combined.1 {
return .loading
}
return combined.0
}
.bind(to: state)
.disposed(by: _disposeBag)
// キャッシュにどんどん反映
_publisher
.compactMap { $0.value }
.bind(to: cache)
.disposed(by: _disposeBag)
}
/// 値の取得を開始
func fetch() {
_fetch()
}
/// 結果を直接受け取りたいときに使う(retryWhenとかできる?できるんか?)
func request() -> Observable<T> {
return _publisher
.take(1)
.flatMap(Observable.from(result:))
.do(onSubscribed: { [weak self] in
self?._fetch()
})
}
private func _fetch() {
guard isLoadingFlag.startLoading() else {
return
}
_request
.subscribe(onSuccess: { [weak self] value in
guard let self = self else {
return
}
self._publisher.accept(.success(value))
self.isLoadingFlag.finishLoading()
}, onError: { [weak self] error in
guard let self = self else {
return
}
self._publisher.accept(.failure(error))
self.isLoadingFlag.finishLoading()
})
.disposed(by: _disposeBag)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment