Created
December 8, 2019 12:02
-
-
Save ha1f/f7135fd0b9732c2ddba58ff42cac85ed 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
enum CachableRequestState { | |
/// 読み込み前 | |
case possible | |
/// 読込中 | |
case loading | |
/// 直近のリクエストがエラー | |
case error(Error) | |
/// 直近のリクエストが成功 | |
case loaded | |
} | |
/// 結果をキャッシュする | |
/// 読込中には重複してリクエストを送らない | |
/// stateとcacheを混ぜたストリームを作らないこと(どっちが先に更新されるかは問わない) | |
final class CachableRequest<T> { | |
/// 状態 | |
let state = BehaviorRelay<CachableRequestState>(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 var _request: Single<T> | |
private let _requestBuilder: AnyCachableRequestRequestBuilder<T> | |
convenience init(_ request: Single<T>) { | |
self.init( | |
requestBuilder: CachableRequestRequestBuilderConstant(request), | |
cacheStrategy: CachableRequestCacheUpdateStrategyReplace() | |
) | |
} | |
init<B: CachableRequestRequestBuilder, S: CachableRequestCacheUpdateStrategy>( | |
requestBuilder: B, | |
cacheStrategy: S | |
) where T == B.T, T == S.T { | |
_requestBuilder = AnyCachableRequestRequestBuilder(requestBuilder) | |
_request = requestBuilder.buildRequest(for: nil) | |
// 直前の結果によってstateを更新 | |
let latestRequestResult = _publisher | |
.map { result -> CachableRequestState in | |
switch result { | |
case .success: | |
return .loaded | |
case let .failure(error): | |
return .error(error) | |
} | |
} | |
Observable | |
.combineLatest(latestRequestResult, isLoadingFlag.value) | |
.map { combined -> CachableRequestState in | |
if combined.1 { | |
return .loading | |
} | |
return combined.0 | |
} | |
.bind(to: state) | |
.disposed(by: _disposeBag) | |
// キャッシュにどんどん反映 | |
// キャッシュのread/writeをtransactionalにしたい | |
_publisher | |
.compactMap { $0.value } | |
.withLatestFrom(cache) { cacheStrategy.combine(current: $1, received: $0) } | |
.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) | |
} | |
} | |
struct AnyCachableRequestRequestBuilder<T>: CachableRequestRequestBuilder { | |
private let _buildRequest: (T?) -> Single<T> | |
init<B: CachableRequestRequestBuilder>( | |
_ requestBuilder: B | |
) where T == B.T { | |
_buildRequest = requestBuilder.buildRequest | |
} | |
func buildRequest(for cache: T?) -> Single<T> { | |
return _buildRequest(cache) | |
} | |
} | |
protocol CachableRequestRequestBuilder { | |
associatedtype T | |
func buildRequest(for cache: T?) -> Single<T> | |
} | |
struct CachableRequestRequestBuilderConstant<T>: CachableRequestRequestBuilder { | |
private let _request: Single<T> | |
init(_ request: Single<T>) { | |
_request = request | |
} | |
func buildRequest(for cache: T?) -> Single<T> { | |
return _request | |
} | |
} | |
protocol CachableRequestCacheUpdateStrategy { | |
associatedtype T | |
func combine(current: T?, received: T) -> T | |
} | |
struct CachableRequestCacheUpdateStrategyReplace<T>: CachableRequestCacheUpdateStrategy { | |
func combine(current: T?, received: T) -> T { | |
return received | |
} | |
} | |
struct CachableRequestCacheUpdateStrategyAppend<E>: CachableRequestCacheUpdateStrategy { | |
func combine(current: [E]?, received: [E]) -> [E] { | |
guard let current = current else { | |
return received | |
} | |
return current + received | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment