最难的问题之一是能够正确地更新“跟踪器”的位置(其显示当前正在播放的歌曲的时间位置和持续时间).这里有几个输入源:
>启动时,遥控器发送网络请求以获取当前播放歌曲的初始位置和持续时间.
>当用户使用遥控器调整跟踪器的位置时,它向音乐应用发送网络请求以改变歌曲的位置.
>如果用户使用桌面上的应用程序更改跟踪器的位置,则应用程序会使用跟踪器的新位置向远程控制器发送网络请求.
>如果当前正在播放歌曲,则跟踪器的位置每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,确保处理来自网络的更新,但仅限于用户之后没有移动滑块?
解决方法
鉴于您的代码和忽略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 */; }];
地图:只是忽略间隔:…信号产生的日期,并取出位置.由于您的请求和来自桌面的消息具有相同的优先级,因此merge:
在一起:
[RACSignal merge:@[desktopPositionSignal,timedRequestSignal]];
但是,如果用户触摸了滑块,您决定不希望这些信号通过.这可以通过两种方式之一完成.使用我建议的标志,你可以合并信号filter:
:
[mergedSignal filter:^BOOL (id _){ return userFiddlingWithSlider; }];
比这更好 – 避免额外的状态 – 将是在throttle:
和sample:
的组合中构建一个操作,该组合在另一个信号没有发送任何信号之后以特定间隔传递信号的值:
[mergedSignal sample: [sliderSignal throttle:kUserFiddlingWithSliderInterval]];
(当然,您可能希望限制/采样间隔:onScheduler:以相同的方式 – 在合并之前发出信号 – 以避免不必要的网络请求.)
您可以将它们全部放在PlayerModel中,将其绑定到位置.您只需要为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风格我会留给你.
顺便说一句,我发现在处理这样的问题时实际绘制信号链很有帮助. I made a quick diagram for your scenario.它有助于将信号视为自身的实体,而不是担心它们带来的价值.