ReactiveCocoa принять до 2-х возможных сигналов?
Поэтому я успешно превратил кнопку в выключатель, который меняет метку.
Я также смог запустить его по расписанию, когда это произойдет, и у него была возможность отключить синхронизированный процесс.
В любом случае мне нужно как-то отключить синхронизированный процесс, и мне было интересно, есть ли способ остановить его без использования одноразового использования. Со вторым сигналом takeUntil.
Edit Я думаю, что то, что я пытался сделать, немного вводило в заблуждение, позвольте мне показать мое текущее решение, которое работает.
-(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];
}
}
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
результирующий цикл обратной связи в конечном итоге приводит к сбою программы при переполнении стека.