2013-10-27 16 views
12

Continuo a girare la testa attorno a RAC e FRP in generale - attualmente non riesco a capire come implementare un pattern che ho comunemente dovuto usare altrove.Invalidazione della cache in ReactiveCocoa

Diciamo che sto facendo un'app flashcard, e la schermata principale è una lista dei miei mazzi di carte. Questa app utilizza lo stato del server di rete come fonte di verità. Non voglio recuperare questa lista di mazzi dal server ogni volta che viene visualizzata la schermata - così eccezionale, posso usare una richiesta di rete differita in un segnale multicast con un oggetto di riproduzione per memoizzare effettivamente tale elenco.

Ho due modi in cui questa lista dovrebbe aggiornarsi recuperando dal server, che è dove per me diventa complicato. Voglio essere in grado di invalidare questa lista "memorizzata nella cache" quando un numero qualsiasi di cose accade nell'app (ad esempio, l'utente naviga su un'altra schermata e fa qualcosa che renderebbe l'elenco dei mazzi sulla schermata iniziale non aggiornato o l'app è appena stato risvegliato, quindi possiamo supporre che potrebbe non essere aggiornato per essere sicuro), in modo che la prossima volta che l'utente ritorni a questa schermata iniziale non mostrerà nulla all'inizio (piuttosto che mostrare la vecchia lista, poiché sa che è non aggiornato a causa dell'azione dell'utente) e recupererà l'elenco, visualizzandolo una volta scaricato. Come posso gestire più elegantemente questo stato "invalidato" (si spera senza stato effettivo)?

Voglio anche essere in grado di scadere l'elenco "memorizzato nella cache" su un timeout - in pratica, il segnale dell'elenco di tabelle fornisce l'elenco memorizzato nella cache fino a quando non è trascorso abbastanza tempo, a quel punto pigramente farà una richiesta di rete prima fornendo i dati.

Ho un paio di idee su come implementare queste due cose, ma sembrano un po 'contorte. Mi piacerebbe avere una guida o essere indicato nella direzione di qualche progetto di esempio.

Un semplice modo che posso vedere per gestire questa situazione è quello di avere un livello di servizio che è di importanza fondamentale, e gestire il caching e invalidazione della cache imperativamente e l'utilizzo di eventi broadcast per invalidare la cache e sia tornare dalla cache o generare una richiesta di rete per popola la cache quando il livello reattivo tenta di accedere ai dati. Preferirei non rimandare a questo metodo senza prima capire il modo reattivo di farlo.

Grazie!

+0

Cross-postato qui: https://github.com/ReactiveCocoa/ReactiveCocoa/issues/899 – aehlke

+1

I love the risposta inviata da @kastiglione in quella discussione. – erikprice

risposta

11

risposta copiato da GitHub

La risposta potrebbe andare per molti versi, il programma di installazione non offre molto vincoli. Detto questo, farò alcuni suggerimenti per iniziare la conversazione.

In primo luogo, dare un'occhiata a +merge:, che consente di combinare una raccolta di segnali "incanalando" i loro valori in un singolo segnale.

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

Con questo in luogo, abbiamo bisogno di trasformare il segnale in uno che recupera i ponti dal server ogni volta che si verifica un evento invalidazione.

Prima di poterlo fare, diamo un'occhiata a come appare la richiesta del segnale. Supponiamo che tu abbia un client API RACified.

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

L'uso di -startWith: è un po 'di trasmettere il pensiero a questo punto. Il piano è quello di formare un segnale che verrà "associato" a una proprietà utilizzando la macro RAC e utilizzando startWith:nil, tale proprietà verrà impostata su nil ogni volta che verrà avviata una nuova richiesta.Questo è quello di seguire il vostro requisito:

spettacolo nulla in un primo momento (piuttosto che mostrare la vecchia lista, dal momento che sa che è fuori di data a causa di un'azione dell'utente) e si ri-prendere la lista

Ora siamo in grado di mappare gli eventi di invalidazione in una richiesta di rete, e sembrerebbe piuttosto semplice, ma manca un po 'di cose.

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

Questo manca di aggiornamento della scadenza. Per fare questo, facciamo un segnale di richiesta che -repeat s dopo un adeguato -delay a seguito del completamento della richiesta precedente:

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

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

Ora, rivisitando l'assegnazione RAC, ha solo bisogno di essere leggermente modificata:

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

C'è ancora un problema con questo, la possibilità che gli eventi di invalidazione causino richieste simultanee al server. Non hai menzionato questo come una preoccupazione, quindi non sono sicuro se ciò è necessario/importante per i casi di utilizzo della tua app, ma è qualcosa da considerare.

Per una panoramica completa, il codice può essere fatto in una singola composizione di segnale:

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

Fantastico! Questo aggiunge molta chiarezza, grazie. Prima però accoppiamo le cose: mi piacerebbe che il mazzo andasse pigramente, piuttosto che non fosse invalidato. Dovrebbe essere solo una volta che è necessario (per la presentazione all'utente, ecc.), Che il RAC (self, decks) rende desideroso se ho capito bene. – aehlke

+0

Se questo viene reso pigro, il problema dovrebbe andare via di diverse richieste avviate contemporaneamente per la stessa risorsa. Sarebbe un problema altrimenti, poiché le chiamate API non sono necessariamente economiche. – aehlke

+0

Sono ancora un po 'confuso riguardo alle best practice relative al RAC, o piuttosto, dove gli effetti collaterali dovrebbero effettivamente accadere. Non è meglio evitare le proprietà se non sono necessarie e utilizzare invece un soggetto di riproduzione? Oppure i soggetti di riproduzione sono altrettanto "cattivi" di una proprietà poiché è solo uno stato in un'altra forma. – aehlke

Problemi correlati