问题
So I have successfully turned a button into an off and on switch that changes the label.
I was also able to have it start a timed processed set off when that is to occur, and it have the ability to shut off the timed process.
Anyways I need to way to shut down the timed process I was wondering if there was a way to stop it without using the disposable. With a second takeUntil signal.
Edit I think what I was trying to do was slightly misleading let me show my current solution that works.
-(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:
The issue with using takeUntil:self.completeSignal
is that when you change completeSignal
to another value, it isn't passed to any function that was already waiting for the variable that completeSignal
was previously holding.
- (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;
}]]
];
}
The signal is now observing and flattening completeSignal
, which will give the desired effect. Signals that complete without sending next events are ignored by takeUntil:
, so use self.completedSignal = [RACSignal return:nil]
, which sends a single next event and then completes.
However, this code is anything but ideal, let's look at a better solution.
@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 {
}
}
Let's run through the list of changes:
completeSignal
is now a RACSubject (a manually controlled RACSignal).- For purity and to get rid of the
@weakify
directive,textChangeSignal
now uses the handyscanWithStart:reduce:
method, which lets you access an accumulator (this works well for methods that work with an incrementing or decrementing number). start
's text is now being changed by therac_liftSelector
function, which takes RACSignals and unwraps them when all have fired.- Your
flattenMap:
to replacepressedStart
with[self startTimer]
now usesscanWithStart:reduce
, which is a much more functional way to keep count.
I'm not sure if you were testing by having updateTable
contain completion signals but it definitely causes a logic issue with your flattenMap:
of pressedButton
, the resulting feedback loop eventually crashes the program when the stack overflows.
来源:https://stackoverflow.com/questions/31092146/reactivecocoa-takeuntil-2-possible-signals