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

 傻要傻到嗨样 发布于 2023-02-10 23:16

我正在制作一个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,确保处理来自网络的更新,但仅限于用户之后没有移动滑块?

1 个回答
  • 在你的GitHub线程中,你说你想要将远程的更新视为规范.这很好,因为(正如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:,然后合并滑块值.由于您在一个链中使用相同的信号多个位置,我相信您想要"多播"它.

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

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

    决定将每个信号分解成自己的变量或将它们串在一起Lisp风格我会留给你.

    顺便说一句,我发现在处理这样的问题时实际绘制信号链很有帮助.我为你的场景制作了一个快速图表.它有助于将信号视为自身的实体,而不是担心它们带来的价值.

    2023-02-10 23:42 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有