ReactiveCocoa中的缓存失效

12

我还在思考如何使用RAC和FRP实现一种常见模式 - 目前正在努力解决这个问题。

假设我正在制作一个单词卡片应用,主屏幕是我的卡组列表。该应用使用网络服务器的状态作为真相来源。我不想每次显示屏幕时重新从服务器获取这个卡组列表 - 所以很好,我可以在具有重放主题的多播信号中使用延迟的网络请求有效地缓存该列表。

我有两种方式可以通过重新从服务器获取来刷新此列表,这就是让我感到困惑的地方。我希望能够在任何应用程序中发生任何事情时使这个“缓存”列表无效(例如,用户导航到其他屏幕并执行会使主屏幕上的卡组列表过时的操作,或者应用程序刚被唤醒,因此我们可以猜测它可能已经过时),这样,下一次用户返回到这个主屏幕时,它将首先显示空白(而不是显示旧列表,因为它知道由于用户的操作它已过时),然后重新获取列表,并在下载完成后显示它。我如何最优雅地处理这个“失效”状态(希望没有实际状态)?

我还希望能够在超时后使“缓存”列表过期 - 基本上,卡组列表信号将提供缓存的列表,直到足够的时间过去,然后它将懒惰地进行网络请求,然后提供数据。

我有几个想法来实现这两个事情,但它们似乎有点复杂。希望得到一些指导或指向一些示例项目。

一种简单的处理方式是使用一个服务层,该服务层是命令式的,并使用广播事件来使缓存失效并在反应性层尝试访问数据时从缓存中返回或产生网络请求以填充缓存。在理解响应式方法之前,我不想转向这种方法。

谢谢!


在这里也有同样的帖子:https://github.com/ReactiveCocoa/ReactiveCocoa/issues/899 - aehlke
1
我喜欢@kastiglione在那个帖子中发布的答案。 - erikprice
1个回答

11

这段回答来自GitHub

这个问题的答案有多种可能,因为没有提供太多限制。以下是我提出的一些建议,以便启动讨论。

首先,看一下+merge:,它允许您通过将所有信号的值"汇聚"到一个信号中来组合信号集合。

RACSignal *deckInvalidated = [[RACSignal merge:@[
    userDidSomethingSignal,
    appReawokenSignal,
    // etc
]];

有了这个设置后,我们需要将该信号转化为一个从服务器获取卡组的信号,在发生失效事件时触发。

在此之前,让我们来看一下信号请求的样子。假设您拥有一个 RACified API 客户端。

RACSignal *fetchDecks = [[APIClient fetchDecks] startWith:nil];

使用-startWith:目前还有一点超前的思考。计划是使用RAC宏将一个信号“绑定”到属性上,并使用startWith:nil,每当开始新请求时,该属性将被设置为nil。这是为了遵循您的要求:

起初不显示任何内容(而不是显示旧列表,因为它知道由于用户的操作已过期),并将重新获取列表

现在我们可以将失效事件映射到网络请求中,看起来很简单,但缺少一些东西。

RAC(self, decks) = [[deckInvalidated mapReplace:fetchDecks] switchToLatest];

这缺乏任何过期刷新功能。为了实现这一点,让我们创建一个请求信号,在前一个请求完成后适当的-delay-repeat

RACSignal *delay = [[RACSignal empty] delay:AEDeckRefreshTimeout];

RACSignal *repeatingFetchDecks = [[fetchDecks concat:delay] repeat];

现在,重新审视一下 RAC 赋值,它只需要稍作修改:

RAC(self, decks) = [[deckInvalidated mapReplace:repeatingFetchDecks] switchToLatest];

还存在一个问题,那就是失效事件可能会导致并发请求发送到服务器。你没有提到这一点是否需要关注或对你的应用程序有重要性,但是需要考虑这个问题。

为了完整概述,代码可以在单个信号组合中完成:

RAC(self, decks) = [[[RACSignal
    merge:@[
        userDidSomethingSignal,
        appReawokenSignal,
    ]]
    mapReplace:[[[[APIClient
        fetchDecks]
        startWith:nil]
        concat:[[RACSignal
            empty]
            delay:AEDeckRefreshTimeout]]
        repeat]]
    switchToLatest];

太棒了!这增加了很多清晰度,谢谢。不过首先有几件事情:我希望卡组获取是懒加载的,而不是在它失效后立即发生。它应该只在需要时(用于向用户展示等)才被获取,如果我理解正确的话,RAC(self, decks)会让它变得急切。 - aehlke
如果将其变为“懒加载”,那么同时针对同一资源启动多个请求的问题应该就会消失。否则,这将是一个问题,因为 API 调用并不一定便宜。 - aehlke
我仍然有些困惑于RAC的最佳实践,或者说,副作用应该发生在哪里才是最好的。如果不需要属性,最好避免使用它们,改用播放主题吗?或者,播放主题和属性一样“糟糕”,因为它只是另一种形式的状态。 - aehlke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接