ReactiveCocoa 中的自动重复倒数计时器

Auto-repeating count down timer in ReactiveCocoa

我是 ReactiveCocoa 的新手,有一个问题我还没有找到解决的方法。我的应用程序中有一个网络请求,其中 returns 数据被编码为二维码,有效期仅为 30 秒。网络请求 returns a RACSignal 并且我将要在该信号中编码的数据发送到我的视图模型。在视图模型中,我将该数据映射到 QR 图像,并在我的视图模型界面中将其显示为 属性。创建 QR 图像后,我想更新一个 timeLeftString 属性 表示 "This code is valid only for 30 seconds" 但秒数会随着时间的推移而变化,在 30 秒完成后,我想制作另一个请求获取另一个有效期为 30 秒的二维码数据,然后完成另一个请求,获取有效期为 30 秒的数据……直到屏幕消失。我该如何实施?

目前我有这个来获取数据:

- (RACSignal *)newPaymentSignal
{
    @weakify(self);
    return [[[[APIManager sharedManager] newPayment] map:^id(NSString *paymentToken) {

        ZXMultiFormatWriter *writer = [ZXMultiFormatWriter writer];
        ZXBitMatrix *result =
            [writer encode:paymentToken format:kBarcodeFormatQRCode width:250 height:250 error:nil];

        if (!result) {
            return nil;
        }

        CGImageRef cgImage = [[ZXImage imageWithMatrix:result] cgimage];
        UIImage *image = [UIImage imageWithCGImage:cgImage];
        return UIImagePNGRepresentation(image);
    }] doNext:^(NSData *data) {
        @strongify(self);
        self.qrImageData = data;
    }];
}

这是定时器

- (RACSignal *)timeRemainingSignal
{
    @weakify(self);
    return [[[RACSignal interval:0.5 onScheduler:[RACScheduler scheduler]]  //
        startWith:[NSDate date]]                                            //
        initially:^{
            @strongify(self);
            self.expiryDate = [[NSDate date] dateByAddingTimeInterval:30];
        }];
}

流程是:从 api 获取数据,启动定时器,当时间到了时发出新请求以获取新数据并再次启动定时器..并永远重复此过程。

1- 从 API 获取数据后如何启动计时器?

2- 如何使此流程永远重复?

3- 如果用户点击用户界面上的按钮,如何在 30 秒完成之前停止计时器并从头开始流程?

4- 我有一个 expiryDate 属性,它在当前日期上增加了 30 秒,因为我想我会用 expiryDate[NSDate date] 的差值来决定时间是否到了 - 有没有更好的方法来实现这个?

5- 当它永远重复时如何中断流程并在屏幕关闭时(或者说,当用户点击另一个按钮时)取消订阅所有内容?

非常感谢您的回答。

我认为缺少的一块拼图是非常有用的 flattenMap 运算符。它本质上是用它返回的信号中的下一个替换它传入信号中的任何下一个。

这是解决您问题的一种方法(我用发送字符串的简单信号替换了您的 newPaymentSignal 方法):

- (RACSignal *)newPaymentSignal
{
    return [[RACSignal return:@"token"] delay:2];
}

- (void)start
{
    NSInteger refreshInterval = 30;

    RACSignal *refreshTokenTimerSignal =
        [[RACSignal interval:refreshInterval onScheduler:[RACScheduler mainThreadScheduler]]
            startWith:[NSDate date]];

    [[[[refreshTokenTimerSignal
        flattenMap:^RACStream *(id _)
        {
            return [self newPaymentSignal];
        }]
        map:^NSDate *(NSString *paymentToken)
        {
            // display paymentToken here
            NSLog(@"%@", paymentToken);

            return [[NSDate date] dateByAddingTimeInterval:refreshInterval];
        }]
        flattenMap:^RACStream *(NSDate *expiryDate)
        {
            return [[[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]]
                startWith:[NSDate date]]
                takeUntil:[refreshTokenTimerSignal skip:1]]
                map:^NSNumber *(NSDate *now)
                {
                    return @([expiryDate timeIntervalSinceDate:now]);
                }];
        }]
        subscribeNext:^(NSNumber *remaining)
        {
            // update timer readout here
            NSLog(@"%@", remaining);
        }];
}

每次外部 refreshTokenTimerSignal 触发时,它都会映射到一个新的 newPaymentSignal,而当它 returns 时,一个值会映射到一个到期日期,该日期被使用创建一个新的 "inner" 每秒触发的定时器信号。

一旦外部刷新计时器发送下一个信号,内部计时器上的 takeUntil 运算符就会完成该信号。

(这里有一件奇怪的事情是我必须在 refreshTokenTimerSignal 中添加一个 skip:1,否则内部定时器永远不会启动。我本以为即使没有 skip:1,也许更精通 RAC 内部结构的人可以解释这是为什么。)

要中断响应各种事件的外部信号流,您也可以尝试使用 takeUntiltakeUntilBlock