Инвалидация кеша в ReactiveCocoa

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

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

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

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

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

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

Спасибо!


person aehlke    schedule 27.10.2013    source источник
comment
Опубликовано здесь: github.com/ReactiveCocoa/ReactiveCocoa/issues/899   -  person aehlke    schedule 27.10.2013
comment
Мне нравится ответ, опубликованный @kastiglione в этой теме.   -  person erikprice    schedule 28.10.2013


Ответы (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];

Здесь отсутствует какое-либо обновление срока действия. Чтобы сделать это, давайте сделаем сигнал запроса, который -repeats после соответствующего -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];
person Dave Lee    schedule 28.10.2013
comment
Фантастический! Это добавляет ясности, спасибо. Однако сначала пара вещей: я бы хотел, чтобы выборка колоды происходила лениво, а не сразу после ее аннулирования. Это должно быть только тогда, когда это необходимо (для презентации пользователю и т. д.), что RAC (я, колоды) делает нетерпеливым, если я правильно понимаю. - person aehlke; 28.10.2013
comment
Если это сделать ленивым, то проблема должна исчезнуть, когда несколько запросов будут запущены одновременно для одного и того же ресурса. В противном случае это было бы проблемой, поскольку вызовы API не обязательно дешевы. - person aehlke; 28.10.2013
comment
Я также все еще немного запутался в лучших практиках RAC, или, скорее, в том, где на самом деле должны возникать побочные эффекты. Не лучше ли избегать свойств, если они не нужны, и вместо этого использовать тему воспроизведения? Или объекты воспроизведения так же плохи, как и свойство, поскольку это просто состояние в другой форме. - person aehlke; 28.10.2013