具有缓存失效和惰性源订阅的缓存

时间:2019-01-27 18:04:15

标签: rx-java rx-java2

我想要什么:

  • 我有一些Observable<T>
  • 我想缓存它的发射值
  • 我只想缓存它发出的值,直到重置缓存为止
  • 缓存无效后,任何订阅者都应自动获取新数据

目标

我创建一个BehaviourRelay并订阅源Observable并缓存其最后一个值。每当缓存失效时,我都会从源中取消订阅中继,然后再次将其重新订阅。

这是我到目前为止所拥有的:

class RxCache<T>(
        private val observable: Observable<T>,
        private val tag: String = "RxCache"
) {

    private val source: Observable<T>
    private var disposable: Disposable? = null
    private val relay = BehaviorRelay.create<T>()

    init {
        // ) 1) create a new hot observable - 
        // as we will subscribe to it after every reload again
        source = observable.share()
                .doAfterNext {
                    L.d(tag, "data loaded")
                }
        // 2) first reload call
        reload()
    }

    fun reload() {
        // 1) unsubscribe from old observable
        disposable?.dispose()
        disposable = null
        // 2) subscribe relay again to reload data
        disposable = source.subscribe(relay)
    }

    fun observe(): Observable<T> {
        return relay.hide()
                .doAfterNext {
                    L.d(tag, "data emitted")
                }
    }
}

问题

我希望relay仅订阅source,如果它本身有一个订阅者,或者第一个订阅者订阅了中继。第一件事很容易,但是我不知道如何以安全的方式解决第二件事。

有什么想法吗?还是其他建议?

2 个答案:

答案 0 :(得分:0)

您已经知道,不可能从BehaviorSubject中“删除”最后一个值。我认为通过多种方式传递内部Observable的代码空手道不是一个好的解决方案。这是可以帮助您的代码:

sealed class CacheItem<T> { // (1)
    class Data<T>(val data: T) : CacheItem<T>()
    class Reset<T> : CacheItem<T>()
}

class RxCache<T> {

    private val behaviorSubject: BehaviorSubject<CacheItem<T>> = BehaviorSubject.create()

    fun reset() {
        behaviorSubject.onNext(CacheItem.Reset()) // (2)
    }

    fun add(newItem: T) {
        behaviorSubject.onNext(CacheItem.Data(newItem)) // (3)
    }

    fun observe() : Observable<T> {
        return behaviorSubject.hide()
            .filter { it != CacheItem.Reset<T>() } // (4)
            .map { (it as CacheItem.Data<T>).data } // (5)
    }
}

让我们解释一些有趣的标记部分:

  1. 我已经创建了CacheItem,它指示subject当前是否保存了缓存的数据,或者是否已执行重新加载/重置。
  2. 每次要“重置”主题时,将Reset对象推向该对象,以指示没有可用的数据。
  3. 如果要添加新的缓存数据,只需将包装在CacheItem.Data类中的数据推送到主题中即可。
  4. 我们要忽略所有重置值,仅接收发出的Data项。
  5. 最后,我们删除CacheItem.Data包装器并获取原始的缓存值。

答案 1 :(得分:0)

我们来做低技术版本:

Map<T, Observable<T> caches = new ConcurrentHashMap<>();

/* .... */
caches.computeIfAbsent(key, k ->  generateSourceObservable(k).cache())
      .doOnNext(...) // or whatever,  continue your processing pipeline.

然后,您只需清除或删除缓存中的键,现有的响应流就不会受到影响。