【问题标题】:Using ReactiveCocoa to track UI updates with a remote object使用 ReactiveCocoa 通过远程对象跟踪 UI 更新
【发布时间】:2013-12-13 16:03:04
【问题描述】:

我正在制作一个 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"]
}

问题是当用户“摆弄”滑块时,将许多网络请求排队,这些请求都在不同的时间返回。当收到响应时,用户可能会再次移动滑块,将滑块移回之前的值。

我的问题:我如何在这个例子中正确使用 ReactiveCocoa,确保处理来自网络的更新,但前提是用户没有移动滑块?

【问题讨论】:

    标签: ios objective-c cocoa-touch uikit reactive-cocoa


    【解决方案1】:

    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。它有助于将信号本身视为实体,而不是担心它们所承载的价值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-04
      • 2012-01-02
      • 1970-01-01
      • 1970-01-01
      • 2016-09-12
      • 1970-01-01
      相关资源
      最近更新 更多