Last active
December 8, 2019 12:03
-
-
Save ha1f/664cbe644e7238d033291bf47d512c6b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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