ReactiveCocoa takeUntil 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, но също така ще се опитам да обясня малко. Сканирането има способността да запазва състояние, което е променливата 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