2013-12-13 9 views
6

Tworzę aplikację na iOS, która umożliwia zdalne sterowanie muzyką w aplikacji odtwarzanej na pulpicie.Używanie ReactiveCocoa do śledzenia aktualizacji interfejsu użytkownika za pomocą zdalnego obiektu

Jednym z najtrudniejszych problemów jest poprawne poprawienie pozycji "trackera" (który pokazuje pozycję czasu i czas trwania aktualnie odtwarzanego utworu). Istnieje kilka źródeł danych wejściowych:

  • Podczas uruchamiania pilot wysyła żądanie sieciowe, aby uzyskać początkową pozycję i czas trwania aktualnie odtwarzanego utworu.
  • Gdy użytkownik dostosuje pozycję trackera za pomocą pilota, wysyła żądanie sieciowe do aplikacji muzycznej, aby zmienić pozycję utworu.
  • Jeśli użytkownik używa aplikacji na pulpicie do zmiany pozycji trackera, aplikacja wysyła żądanie sieciowe do pilota z nową pozycją trackera.
  • Jeśli utwór jest aktualnie odtwarzany, pozycja trackera jest aktualizowana co 0,5 sekundy.

W tej chwili tracker jest urządzeniem UISlider, które jest wspierane przez model "odtwarzacza". Gdy użytkownik zmienia pozycję na suwaku, aktualizuje model i wysyła żądanie sieciowe, tak jak poniżej:

W NowPlayingViewController.m

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

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

W 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"] 
} 

Problem polega na tym, że użytkownik "skrzypi" za pomocą suwaka i umieszcza w kolejce wiele żądań sieciowych, które powracają w różnym czasie. Użytkownik może ponownie przesunąć suwak po otrzymaniu odpowiedzi, przesuwając suwak z powrotem do poprzedniej wartości.

Moje pytanie: Jak prawidłowo korzystać z ReactiveCocoa w tym przykładzie, upewniając się, że aktualizacje z sieci są obsługiwane, ale tylko wtedy, gdy użytkownik nie przesunął suwaka?

Odpowiedz

6

W your GitHub thread about this mówisz, że chcesz uważać aktualizacje zdalnego jako kanoniczne. To dobrze, ponieważ (jak zasugerował tam Josh Abernathy), RAC lub nie, musisz wybrać jedno z dwóch źródeł, aby uzyskać pierwszeństwo (lub potrzebujesz sygnatur czasowych, ale potrzebujesz zegara referencyjnego ...).

Biorąc pod uwagę twój kod i ignorując RAC, rozwiązaniem jest ustawienie flagi w seekToPosition: i rozbrojenie jej przy użyciu licznika czasu. Sprawdź flagę pod numerem recievedPlayerUpdate:, ignorując aktualizację, jeśli jest ustawiona.

Nawiasem mówiąc, należy użyć RAC() makro powiązać wartość Twojego suwaka, zamiast subscribeNext: że masz:

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

Można zdecydowanie zbudować łańcuch sygnału robić, co chcesz, chociaż. Masz cztery sygnały, które musisz połączyć.

Na ostatniej pozycji, okresowa aktualizacja, można użyć interval:onScheduler::

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

map: prostu ignoruje datę sygnał interval:... produkuje i pobiera pozycję.Ponieważ wasze prośby i komunikaty z pulpitu mają równy priorytet, merge: tym razem:

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

Zdecydowałeś, że nie chcą żadnego z tych sygnałów przechodzących jeśli użytkownik dotknął suwak, choć. Można to osiągnąć na jeden z dwóch sposobów. Z flagą I zasugerował, że można filter: połączyła sygnału:

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

lepiej niż to - unikanie dodatkowy stan - byłoby zbudować operację Spośród kombinacji throttle: i sample: która przechodzi wartość z sygnał w pewnym odstępie po drugim sygnale niczego nie wysłał:

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

(I może, oczywiście, chcą zdławić/próbkowania sygnału interval:onScheduler: w taki sam sposób - przed scaleniem - w celu unikaj niepotrzebnych żądań sieciowych.)

Możesz to wszystko połączyć w PlayerModel, wiążąc go z position. Po prostu musisz podać PlayerModel suwakowi rac_signalForControlEvents:, a następnie połączyć wartość suwaka. Ponieważ używasz tego samego sygnału w wielu miejscach w jednym łańcuchu, wierzę, że chcesz go uzyskać.

Na koniec użyj startWith:, aby uzyskać pierwszy element powyżej, pozycję początkową z aplikacji komputerowej, do strumienia.

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

Decyzja o rozbiciu każdego sygnału na własną zmienną lub połącz je wszystkie w stylu Lisp Zostawię dla ciebie.

Nawiasem mówiąc, przydaje się wyciąganie łańcuchów sygnałów podczas pracy nad takimi problemami. I made a quick diagram for your scenario. Pomaga w myśleniu o sygnałach jako jednostkach, w przeciwieństwie do martwienia się o wartości, które niosą.

Powiązane problemy