2013-12-13 23 views
6

Realizzo un'app per iOS che consente di controllare a distanza la musica in un'app in esecuzione sul desktop.Utilizzo di ReactiveCocoa per tenere traccia degli aggiornamenti dell'interfaccia utente con un oggetto remoto

Uno dei problemi più difficili è riuscire ad aggiornare correttamente la posizione del "tracker" (che mostra la posizione temporale e la durata della canzone attualmente in riproduzione). Esistono diverse fonti di input:

  • All'avvio, il telecomando invia una richiesta di rete per ottenere la posizione iniziale e la durata della canzone attualmente in riproduzione.
  • Quando l'utente regola la posizione del localizzatore tramite il telecomando, invia una richiesta di rete all'app musicale per modificare la posizione della canzone.
  • Se l'utente utilizza l'app sul desktop per modificare la posizione del tracker, l'app invia una richiesta di rete al telecomando con la nuova posizione del tracker.
  • Se la canzone è attualmente in riproduzione, la posizione del tracker viene aggiornata ogni 0,5 secondi circa.

Al momento, il tracker è un UISlider supportato da un modello "Player". Ogni volta che l'utente cambia la posizione sul cursore, aggiorna il modello e invia una richiesta di rete, in questo modo:

In NowPlayingViewController.m

[[slider rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UISlider *x) { 
    [playerModel seekToPosition:x.value]; 
}]; 

[RACObserve(playerModel, position) subscribeNext:^(id x) { 
    slider.value = player.position; 
}]; 

In PlayerModel.m:

@property (nonatomic) NSTimeInterval position; 

- (void)seekToPosition:(NSTimeInterval)position 
{ 
    self.position = position; 
    [self.client newRequestWithMethod:@"seekTo" params:@[positionArg] callback:NULL]; 
} 

- (void)receivedPlayerUpdate:(NSDictionary *)json 
{ 
    self.position = [json objectForKey:@"position"] 
} 

Il problema è quando un utente "armeggia" con il dispositivo di scorrimento e accoda un numero di richieste di rete che ritornano tutte in momenti diversi. L'utente potrebbe aver spostato di nuovo il cursore quando viene ricevuta una risposta, riportando il cursore su un valore precedente.

La mia domanda: come utilizzare correttamente ReactiveCocoa in questo esempio, assicurando che gli aggiornamenti dalla rete siano gestiti, ma solo se l'utente non ha spostato il cursore da allora?

risposta

6

In your GitHub thread about this si dice che si desidera considerare gli aggiornamenti del telecomando come canonici. Va bene, perché (come ha suggerito Josh Abernathy), RAC o no, devi scegliere una delle due fonti per avere la priorità (o hai bisogno di un timestamp, ma poi hai bisogno di un orologio di riferimento ...).

Dato il codice e ignorando RAC, la soluzione è solo impostando un flag in seekToPosition: e disattivandolo utilizzando un timer. Controlla il flag in recievedPlayerUpdate:, ignorando l'aggiornamento se è impostato.

Tra l'altro, è necessario utilizzare la macro RAC() di legare il valore del tuo dispositivo di scorrimento, piuttosto che il subscribeNext: che hai:

RAC(slider, value) = RACObserve(playerModel, position); 

Si può sicuramente costruire una catena di segnale per fare quello che vuoi, anche se. Hai quattro segnali che devi combinare.

Per l'ultimo elemento, il periodico aggiornamento, è possibile utilizzare interval:onScheduler::

[[RACSignal interval:kPositionFetchSeconds 
     onScheduler:[RACScheduler scheduler]] map:^(id _){ 
          return /* Request position over network */; 
}]; 

Il map: ignora solo la data che il segnale interval:... produce, e recupera la posizione.Dal momento che le vostre richieste e messaggi dal desktop hanno la stessa priorità, merge: quelli insieme:

[RACSignal merge:@[desktopPositionSignal, timedRequestSignal]]; 

È deciso che non si desidera uno di quei segnali che attraversano se l'utente ha toccato il cursore, però. Questo può essere realizzato in due modi. Utilizzando la bandiera ho suggerito, si potrebbe filter: che fuse segnale:

[mergedSignal filter:^BOOL (id _){ return userFiddlingWithSlider; }]; 

Meglio di così - evitare stato aggiunto - sarebbe quello di costruire un'operazione da una combinazione di throttle: e sample: che passa un valore da un segnale a un certo intervallo dopo l'altro segnale non ha inviato nulla:

[mergedSignal sample: 
      [sliderSignal throttle:kUserFiddlingWithSliderInterval]]; 

(E si potrebbe, naturalmente, vogliono strozzare/campionare il segnale interval:onScheduler: nello stesso modo - prima della fusione - al fine di evitare richieste di rete non necessarie.)

È possibile raggruppare tutto in PlayerModel, collegandolo a position. Dovrai solo fornire lo rac_signalForControlEvents: del dispositivo di scorrimentoe quindi unire il valore del cursore. Dal momento che stai utilizzando lo stesso segnale in più punti in una catena, credo che tu voglia il numero "multicast".

Infine, utilizzare startWith: per ottenere il primo elemento sopra, la posizione iniziale dall'app desktop, nello stream.

RAC(self, position) = 
    [[RACSignal merge:@[sampledSignal, 
         [sliderSignal map:^id(UISlider * slider){ 
                return [slider value]; 
         }]] 
] startWith:/* Request position over network */]; 

La decisione di interrompere ogni segnale nella propria variabile o di raggrupparli tutti in stile Lisp Vi lascio.

Per inciso, ho trovato utile disegnare effettivamente le catene di segnale quando si lavora su problemi come questo. I made a quick diagram for your scenario. Aiuta a pensare i segnali come entità a sé stanti, al contrario di preoccuparsi dei valori che portano.

Problemi correlati