Недействительность кэша в ReactiveCocoa

Я все еще обнимаю RAC и FRP в целом - в настоящее время я пытаюсь понять, как реализовать шаблон, который я обычно использовал в других местах.

Скажем, я делаю приложение для флеш-карт, а основной экран - это список моих колод карт. Это приложение использует состояние сетевого сервера как источник правды. Я не хочу повторно извлекать этот список колод с сервера каждый раз, когда отображается экран - так здорово, что я могу использовать отложенный сетевой запрос в многоадресном сигнале с темой воспроизведения, чтобы эффективно сохранить этот список.

У меня есть два способа, когда этот список должен обновляться путем повторной выборки с сервера, где он становится сложным для меня. Я хочу, чтобы иметь возможность аннулировать этот "кешированный" список, когда в приложении происходит какое-либо событие (например, пользователь переходит к другому экрану и делает то, что сделает список колод на домашнем экране устаревшим, или приложение был просто реален, поэтому мы можем догадаться, что он может быть устаревшим, чтобы быть в безопасности), так что в следующий раз, когда пользователь вернется на этот главный экран, он сначала ничего не покажет (вместо того, чтобы показывать старый список, поскольку он знает это устаревший из-за действия пользователя) и будет повторно извлекать список, отображая его после его загрузки. Как я могу наиболее элегантно обрабатывать это "недействительное" состояние (надеюсь, без фактического состояния)?

Я также хочу, чтобы истечь "кешированный" список по истечении тайм-аута - в основном, сигнал списка колод будет выдавать кеш-список до тех пор, пока не пройдет достаточно времени, и в этот момент он лениво сделает сетевой запрос до предоставления данные.

У меня есть пара идей о том, как реализовать эти две вещи, но они кажутся немного запутанными. Хотелось бы получить какое-то руководство или указать направление в каком-то примерном проекте.

Простым способом, с помощью которого я могу справиться с этим, является наличие обязательного уровня обслуживания и принудительное кэширование и кэширование, а также использование широковещательных событий для аннулирования кеша и возврат из кеша или создание сетевого запроса для заполнения кеш, когда реактивный уровень пытается получить доступ к данным. Я бы предпочел не откладывать этот метод без предварительного понимания реактивного способа этого.

Спасибо!

Ответ 1

Ответ, скопированный с 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];

В этом не хватает освежающего времени. Чтобы сделать это, дайте сигнал запроса -repeat после соответствующего -delay после завершения предыдущего запроса:

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];