ReactiveCocoa中自动重复倒计时器

aka*_*lar 0 timer objective-c ios reactive-cocoa

我是ReactiveCocoa的新手,有一个问题我还没找到解决办法.我的应用程序中有一个网络请求,它返回要编码的数据,QR码只有30秒有效.网络请求返回a RACSignal并且我将要在该信号中编码的数据发送到我的视图模型.在视图模型中,我将该数据映射到QR图像,并将其作为视图模型界面中的属性公开.在我创建QR图像后,我想更新一个timeLeftString显示"此代码仅在30秒内有效"的属性,但秒数将随着时间的推移而变化,在完成30秒后,我想再次请求获取另一个有效30秒的QR码数据,之后完成另一个请求,获取数据将有效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;
    }];
}
Run Code Online (Sandbox Code Playgroud)

这对于计时器

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

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

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

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

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

4-我有一个expiryDate属性,在当前日期增加了30秒,因为我认为我将采取差异expiryDate[NSDate date]决定时间是否到了 - 是否有更好的方法来实现这一点?

5-当屏幕被解除时(或者,当用户点击另一个按钮时),如何在永久重复时取消流量并取消订阅所有内容?

非常感谢您的答案.

Eri*_*k J 5

我认为拼图的缺失部分是非常有用的flattenMap操作员.它基本上用来自其返回的信号的nexts替换其输入信号中的任何nexts.

这是解决问题的一种方法(我使用发送字符串的简单信号替换了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);
        }];
}
Run Code Online (Sandbox Code Playgroud)

每次外部refreshTokenTimerSignal触发时,它都会映射到一个新的newPaymentSignal,当它返回一个值时,它会被映射到一个有效期,用于创建一个新的"内部"计时器信号,每秒触发一次.

takeUntil在内侧定时器操作者只要外刷新定时器发送下一完成该信号.

(这里的一个奇怪的事情是,我有一个添加skip:1refreshTokenTimerSignal,否则内部计时器就不会开始了.我本来期望它没有甚至工作skip:1,也许有人在RAC的内部更好的熟悉可以解释这是为什么.)

为了响应各种事件来打破外部信号的流动,你也可以尝试使用takeUntiltakeUntilBlock使用它.