Ios 使用ReactiveCocoa跟踪远程对象的UI更新

Ios 使用ReactiveCocoa跟踪远程对象的UI更新,ios,objective-c,cocoa-touch,uikit,reactive-cocoa,Ios,Objective C,Cocoa Touch,Uikit,Reactive Cocoa,我正在制作一个iOS应用程序,它可以让你远程控制在桌面上播放的应用程序中的音乐 最困难的问题之一是能够正确地更新“跟踪器”(显示当前播放歌曲的时间位置和持续时间)的位置。这里有几个输入来源: 在启动时,遥控器发送网络请求以获取当前播放歌曲的初始位置和持续时间 当用户使用遥控器调整跟踪器的位置时,它会向音乐应用程序发送网络请求,以更改歌曲的位置 如果用户使用桌面上的应用程序更改跟踪器的位置,该应用程序将向远程设备发送一个网络请求,其中包含跟踪器的新位置 如果当前正在播放歌曲,则跟踪器的位置每0.

我正在制作一个iOS应用程序,它可以让你远程控制在桌面上播放的应用程序中的音乐

最困难的问题之一是能够正确地更新“跟踪器”(显示当前播放歌曲的时间位置和持续时间)的位置。这里有几个输入来源:

  • 在启动时,遥控器发送网络请求以获取当前播放歌曲的初始位置和持续时间
  • 当用户使用遥控器调整跟踪器的位置时,它会向音乐应用程序发送网络请求,以更改歌曲的位置
  • 如果用户使用桌面上的应用程序更改跟踪器的位置,该应用程序将向远程设备发送一个网络请求,其中包含跟踪器的新位置
  • 如果当前正在播放歌曲,则跟踪器的位置每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"]
}
问题在于,当用户“摆弄”滑块时,会将大量在不同时间返回的网络请求排队。用户可能在收到响应时再次移动滑块,将滑块移回先前的值

我的问题是:在这个例子中,我如何正确使用ReActucCoCo,确保网络中的更新被处理,但是只有当用户没有移动滑块之后?

< P>在你说你想把远程更新看作是规范的。这很好,因为(正如Josh Abernathy在那里所建议的那样),无论是否使用RAC,您都需要从两个源中选择一个作为优先级(或者您需要时间戳,但随后需要一个参考时钟…)

给定您的代码并忽略RAC,解决方案就是在
中设置一个标志,然后使用计时器将其取消设置。检查
receivedplayeruupdate:
中的标志,忽略已设置的更新

顺便说一句,您应该使用
RAC()
宏来绑定滑块的值,而不是您得到的
subscribeText:

RAC(slider, value) = RACObserve(playerModel, position);
不过,你完全可以构建一个信号链来做你想做的事情。你有四个信号需要组合

对于最后一项定期更新,您可以使用:

映射:
只是忽略
间隔:…
信号产生的日期,并获取位置。由于来自桌面的请求和消息具有相同的优先级,因此这些请求和消息加在一起:

[RACSignal merge:@[desktopPositionSignal, timedRequestSignal]];
但是,您决定,如果用户触摸了滑块,您不希望这些信号中的任何一个通过。这可以通过以下两种方式之一实现。使用我建议的标志,您可以使用该合并信号:

[mergedSignal filter:^BOOL (id _){ return userFiddlingWithSlider; }];
比这更好的方法是——避免额外的状态——用and的组合构建一个操作,在另一个信号没有发送任何信息后,以一定的间隔从一个信号传递一个值:

[mergedSignal sample:
          [sliderSignal throttle:kUserFiddlingWithSliderInterval]];
(当然,为了避免不必要的网络请求,您可能希望在合并之前以相同的方式限制/采样
interval:onScheduler:
信号。)

您可以将这一切放在
PlayerModel
中,并将其绑定到
位置
。您只需要为
PlayerModel
提供滑块的
rac\u signalForControlEvents:
,然后合并到滑块值中。因为你在一条链中的多个地方使用同一个信号,我相信你想要它

最后,使用将上面的第一项(桌面应用程序的初始位置)放入流中

RAC(self, position) = 
    [[RACSignal merge:@[sampledSignal,
                        [sliderSignal map:^id(UISlider * slider){
                                                    return [slider value];
                        }]]
] startWith:/* Request position over network */];
我将把每个信号分解成自己的变量,或者用Lisp风格将它们串在一起

顺便说一句,我发现在处理这样的问题时,绘制信号链是很有帮助的。我它有助于将信号本身视为实体,而不是担心它们所承载的价值

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