ReactiveCocoa принимать до 2 возможных сигналов?

Итак, я успешно превратил кнопку в выключатель, который меняет метку.

Я также смог запустить процесс обработки по времени, когда это должно произойти, и у него есть возможность отключить процесс по времени.

В любом случае, мне нужно остановить процесс по времени. Мне было интересно, есть ли способ остановить его, не используя одноразовые. Со вторым сигналом takeUntil.

Изменить. Я думаю, что то, что я пытался сделать, немного вводило в заблуждение, позвольте мне показать мое текущее решение, которое работает.

-(RACSignal*) startTimer {
    return [[RACSignal interval:1.0
            onScheduler:[RACScheduler mainThreadScheduler]]
             startWith:[NSDate date]];
}

-(void) viewWillAppear:(BOOL)animated {}

-(void) viewDidLoad {

    self.tableView.delegate = self;
    self.tableView.dataSource = self;

    RACSignal* pressedStart = [self.start rac_signalForControlEvents:UIControlEventTouchUpInside];

    @weakify(self);
    RACSignal* textChangeSignal = [pressedStart map:^id(id value) {
        @strongify(self);
        return [self.start.titleLabel.text isEqualToString:@"Start"] ? @"Stop" : @"Start";
    }];

    // Changes the title
    [textChangeSignal subscribeNext:^(NSString* text) {
        @strongify(self);
        [self.start setTitle:text forState:UIControlStateNormal];
    }];

    RACSignal* switchSignal = [[textChangeSignal map:^id(NSString* string) {
        return [string isEqualToString:@"Stop"] ? @0 : @1;

    }] filter:^BOOL(id value) {
        NSLog(@"Switch %@",value);
        return [value boolValue];
    }];


    [[self rac_signalForSelector:@selector(viewWillAppear:)]
     subscribeNext:^(id x) {


    }];

    static NSInteger t = 0;
    // Remake's it self once it is on finished.
    self.disposable = [[[textChangeSignal filter:^BOOL(NSString* text) {
        return [text isEqualToString:@"Stop"] ? [@1 boolValue] : [@0 boolValue];
    }]
                                  flattenMap:^RACStream *(id value) {
                                      NSLog(@"Made new Sheduler");
                                      @strongify(self);
                                      return [[self startTimer] takeUntil:switchSignal];
                                  }] subscribeNext:^(id x) {
                                      NSLog(@"%@",x);
                                      @strongify(self);
                                      t = t + 1;
                                      NSLog(@"%zd",t);

                                      [self updateTable];
                                  }];

    [[self rac_signalForSelector:@selector(viewWillDisappear:)] subscribeNext:^(id x) {
        NSLog(@"viewWillAppear Dispose");
        [self.disposable dispose];
    }];

}


-(BOOL) isGroupedExcercisesLeft {
    BOOL isGroupedLeft = NO;
    for (int i =0;i < [self.excercises count]; i++) {
        Excercise* ex = [self.excercises objectAtIndex:i];
        if(ex.complete == NO && ex.grouped == YES) {
            isGroupedLeft = YES;
            break;
        }
    }
    return isGroupedLeft;
}

-(void) updateTable {

    // Find the
    NSInteger nextRow;

    if (([self.excercises count] > 0 || self.excercises !=nil) && [self isGroupedExcercisesLeft]) {

        for (int i =0;i < [self.excercises count]; i++) {

            Excercise* ex = [self.excercises objectAtIndex:i];
            if(ex.complete == NO && ex.grouped == YES) {
                nextRow = i;
                break;
            }

        }

        NSIndexPath* path = [NSIndexPath indexPathForItem:nextRow inSection:0];
        NSArray* indexPath = @[path];
        // update //

        Excercise* ex = [self.excercises objectAtIndex:nextRow];
        [self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES];
        if (ex.seconds <= 0) {
            RLMRealm* db = [RLMRealm defaultRealm];
            [db beginWriteTransaction];
            ex.complete = YES;
            [db commitWriteTransaction];
        }
        else {
            // Update Seconds
            RLMRealm* db = [RLMRealm defaultRealm];
            [db beginWriteTransaction];
            ex.seconds = ex.seconds - 1000;
            NSLog(@"Seconds: %zd",ex.seconds);
            [db commitWriteTransaction];
            // Update table
            [self.tableView reloadRowsAtIndexPaths:indexPath withRowAnimation:UITableViewRowAnimationNone];
        }


    } else {
        NSLog(@"Done");

        SIAlertView *alertView = [[SIAlertView alloc] initWithTitle:@"Deskercise" andMessage:@"Excercises Complete"];
        [alertView addButtonWithTitle:@"Ok"
                                 type:SIAlertViewButtonTypeDefault
                              handler:^(SIAlertView *alert) {

                              }];

        alertView.transitionStyle = SIAlertViewTransitionStyleBounce;
        [alertView show];
        NSLog(@"Dispose");
        [self.disposable dispose];

    }

}

person George Host    schedule 27.06.2015    source источник


Ответы (1)


Проблема с использованием takeUntil:self.completeSignal заключается в том, что когда вы меняете completeSignal на другое значение, оно не передается ни одной функции, которая уже ожидала переменную, которую ранее удерживала completeSignal.

- (RACSignal*) startTimer {
    @weakify(self)
    return [[[RACSignal interval:1.0
                 onScheduler:[RACScheduler mainThreadScheduler]]
         startWith:[NSDate date]]
        takeUntil:[[self.start rac_signalForControlEvents:UIControlEventTouchUpInside]
                   merge:[[RACObserve(self, completeSignal) skip:1] flattenMap:
                          ^RACStream *(RACSignal * signal) {
                              @strongify(self)
                              return self.completeSignal;
                          }]]
        ];
}

Теперь сигнал наблюдает и сглаживает completeSignal, что даст желаемый эффект. Сигналы, которые завершаются без отправки следующих событий, игнорируются takeUntil:, поэтому используйте self.completedSignal = [RACSignal return:nil], который отправляет одно следующее событие, а затем завершается.

Однако этот код далеко не идеален, давайте посмотрим на лучшее решение.

@property (nonatomic, readwrite) RACSubject * completeSignal;

- (RACSignal*) startTimer {
    return [[[RACSignal interval:1.0
                 onScheduler:[RACScheduler mainThreadScheduler]]
         startWith:[NSDate date]]
        takeUntil:[[self.start rac_signalForControlEvents:UIControlEventTouchUpInside]
                   merge:self.completeSignal]
        ];
}
- (void) viewDidLoad {
    [super viewDidLoad];
    self.completeSignal = [RACSubject subject];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;

    RACSignal * pressedStart = [self.start rac_signalForControlEvents:UIControlEventTouchUpInside];

    @weakify(self);
    RACSignal* textChangeSignal = [[pressedStart startWith:nil] scanWithStart:@"Stop" reduce:^id(id running, id next) {
        return @{@"Start":@"Stop", @"Stop":@"Start"}[running];
    }];

    [self.start
     rac_liftSelector:@selector(setTitle:forState:)
     withSignals:textChangeSignal, [RACSignal return:@(UIControlStateNormal)], nil];

    [[[pressedStart flattenMap:^RACStream *(id value) { //Using take:1 so that it doesn't get into a feedback loop
        @strongify(self);
        return [self startTimer];
    }] scanWithStart:@0 reduce:^id(NSNumber * running, NSNumber * next) {
        return @(running.unsignedIntegerValue + 1);
    }] subscribeNext:^(id x) {
        @strongify(self);
        [self updateTable];
        NSLog(@"%@", x);
    }];
}

- (void) updateTable {
    //If you uncomment these then it'll cause a feedback loop for the signal that calls updateTable

    //[self.start sendActionsForControlEvents:UIControlEventTouchUpInside];
    //[self.completeSignal sendNext:nil];
    if ([self.excercises count] > 0 || self.excercises !=nil) {
    } else {
    }
}

Пробежимся по списку изменений:

  • completeSignal теперь является RACSubject (RACSignal, управляемый вручную).
  • Для чистоты и чтобы избавиться от директивы @weakify, textChangeSignal теперь использует удобный метод scanWithStart:reduce:, который позволяет вам получить доступ к аккумулятору (это хорошо работает для методов, которые работают с увеличивающимся или уменьшающимся числом).
  • Текст start теперь изменяется функцией rac_liftSelector, которая принимает сигналы RACSignals и разворачивает их, когда все сработают.
  • Ваш flattenMap: для замены pressedStart на [self startTimer] теперь использует scanWithStart:reduce, что является гораздо более функциональным способом вести счет.

Я не уверен, что вы тестировали, имея updateTable сигналы завершения, но это определенно вызывает логическую проблему с вашим flattenMap: из pressedButton, получающийся в результате цикл обратной связи в конечном итоге приводит к сбою программы, когда стек переполняется.

person Charles Maria    schedule 28.06.2015
comment
Есть ли мраморная диаграмма на скане? Я не совсем уверен, как это работает. - person George Host; 28.06.2015
comment
rxmarbles.com/#scan, но я также попытаюсь немного объяснить. Scan имеет возможность сохранять состояние, которым является переменная running, аргумент с состоянием, который передается для каждого блока и заменяется тем, что возвращается этим блоком. Это делает его очень полезным для сигнала, который может переключаться между двумя разными выходами, такими как @Stop и @Start, на основе предыдущего выхода. - person Charles Maria; 29.06.2015
comment
Итак, я предполагаю, что сканирование с запуском сначала применяет сигнал @0, прежде чем он обработает следующий сигнал? - person George Host; 29.06.2015
comment
Правильно, первая переменная, которую subscribeNext: увидит, это @1. - person Charles Maria; 29.06.2015
comment
Поэтому я не уверен, как слияние предотвратит остановку планирования самообновления таблицы. Можете ли вы объяснить, как оно это делает? Итак, чтобы уточнить, я хочу, чтобы этот триггер останавливал вызов функции [self updatetable], который у меня есть в моем отредактированном решении с использованием одноразового. - person George Host; 29.06.2015
comment
Слияние — это просто синтаксический сахар, поэтому вам не нужно использовать две функции takeUntil:. Я тестировал переменную UIButton и completeSignal в качестве RACSubject, и отправка UIControlEventTouchUpInside и отправка субъектом следующего события приводят к прекращению срабатывания сигнала. - person Charles Maria; 29.06.2015
comment
Давайте продолжим это обсуждение в чате. - person Charles Maria; 29.06.2015