2013-12-13 37 views
6

我正在製作一個iOS應用程序,它可以讓您遠程控制桌面上播放的應用程序中的音樂。使用ReactiveCocoa跟蹤UI更新與遠程對象

最難的問題之一是能夠正確更新「跟蹤器」(顯示當前播放歌曲的時間位置和持續時間)的位置。這裏有幾種輸入源:

  • 在啓動時,遠程發送網絡請求以獲取當前播放歌曲的初始位置和持續時間。
  • 當用戶使用遙控器調整跟蹤器的位置時,它會向音樂應用發送網絡請求以更改歌曲的位置。
  • 如果用戶使用桌面上的應用程序更改跟蹤器的位置,應用程序將向跟蹤器的新位置發送網絡請求。
  • 如果歌曲正在播放,則跟蹤器的位置每0.5秒左右更新一次。

目前,跟蹤器是一個UISlider,它是由一個「玩家」模型支持。每當用戶改變在滑塊上的位置時,它更新模型和發送的網絡請求,例如:

在NowPlayingViewController.m

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

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

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

問題在於用戶「滑過」滑塊,並排隊一些網絡請求,這些請求都會在不同的時間返回。當收到響應時,用戶可能會再次移動滑塊,將滑塊移回到先前的值。

我的問題:在這個例子中,我如何正確使用ReactiveCocoa,確保來自網絡的更新得到處理,但只有在用戶還沒有移動滑塊後?

回答

6

your GitHub thread about this你說你想把遙控器的更新視爲規範。這很好,因爲(如Josh Abernathy在那裏建議的那樣),RAC與否,你需要選擇兩個來源中的一個來優先考慮(或者你需要時間戳,但是你需要一個參考時鐘......)。

考慮到您的代碼並忽視RAC,該解決方案只需在seekToPosition:中設置一個標誌並使用定時器取消設置。檢查recievedPlayerUpdate:中的標誌,如果已設置,則忽略更新。

順便說一句,你應該使用RAC()宏綁定您的滑塊的價值,而不是subscribeNext:你已經有了:

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

你絕對可以構建一個信號鏈,做你想做的,雖然。你需要結合四個信號。

對於最後一個項目,定期更新,你可以使用interval:onScheduler:

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

map:只是忽略了日期的interval:...信號產生,並獲取位置。由於從桌面您的請求和消息具有相同的優先級,merge:那些一起:

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

你決定,你不想要或者那些經歷,如果用戶已經觸摸滑塊的信號,雖然。這可以通過兩種方式之一來完成。採用我建議的標誌,你可以filter:該合併的信號:比

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

更好 - 避免多餘的狀態 - 將打造出的throttle:sample:的組合,從一個傳遞一個值的操作在另一個信號經過一定時間間隔信號沒有發出任何東西:

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

(你可能,當然,想油門/樣品中相同的方式interval:onScheduler:信號 - 在合併之前 - 爲了避免不必要的網絡請求。)

你可以把這一切放在PlayerModel,綁定到position。您只需要輸入PlayerModel滑塊的rac_signalForControlEvents:,然後合併滑塊值即可。由於你在一個鏈中使用了多個相同的信號,我相信你想要"multicast"它。

最後,使用startWith:將上面的第一項(來自桌面應用程序的初始位置)放到流中。

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

決定將每個信號分解成它自己的變量或將它們串在一起Lisp風格我會留給你。

順便說一句,我發現在解決這樣的問題時,實際上可以繪製出信號鏈。我made a quick diagram for your scenario。它有助於將信號視爲實體本身,而不是擔心它們帶來的價值。