简体   繁体   中英

Reactive Cocoa Splitting a signal without code duplication?

I am trying to change the label on a button up on the selector being called.

It appears that the code is duplicated. Is there a way perhaps it's not obvious to me right now to have the signal switch after the map ? or no ?

[[[pressedStart map:^id(id value) {
    UIButton* button = value;
    BOOL transform = [button.titleLabel.text isEqualToString:@"Start"];
    return [NSNumber numberWithBool:transform];

}] filter:^BOOL(id value) {
    return [value boolValue];
}] subscribeNext:^(id x) {
    self.start.titleLabel.text = @"Stop";
}];

[[[pressedStart map:^id(id value) {
    UIButton* button = value;
    BOOL transform = [button.titleLabel.text isEqualToString:@"Stop"];
    return [NSNumber numberWithBool:transform];
}] filter:^BOOL(id value) {
    return [value boolValue];
}] subscribeNext:^(id x) {
   self.start.titleLabel.text = @"Start";
}];

First of all, in order to change the button's title, you have to call its setTitle:forState: method.

Also please note that using self inside the subscribeNext block is likely to create a retain cycle (and therefore a memory leak). You can read more about it in this answer . You can use @weakify / @strongify macros or, as mentioned in that answer, use rac_liftSelectors:withSignals: method (which IMHO seems to be cleaner).

Your code can be simplified as you actually don't need to split the signal at all. You can use a simple condition inside the map block and return the value which should be the button's title after it was pressed. This value will be sent as a next value of the resulting signal. You can also use startWith: operator to set the initial value (I guess it should be "Start").

RACSignal *buttonTextSignal = [[pressedStart map:^id(UIButton *buttonPressed) {
    return [buttonPressed.titleLabel.text isEqualToString:@"Start"] ? @"Stop" : @"Start";
}]
startWith:@"Start"];
[self.start rac_liftSelector:@selector(setTitle:forState:) withSignals:buttonTextSignal, [RACSignal return:@(UIControlStateNormal)], nil];

What does rac_liftSelector:withSignals: do? Each time one of the signals sends its next value, it invokes the method identified by the selector (in this case setTitle:forState: ). The method is invoked with next values of the signals as its parameters. So in our case it will initially call:

[self.startButton setTitle:@"Start" forState:UIControlStateNormal];

If you wanted to set a single property (let's say titleLabel.text ), you could bind it with RAC macro:

RAC(self.startButton, titleLabel.text) = buttonTextSignal;

Unfortunately, it only works for setting properties, and in your case you have to call a method with two arguments, that's why you have to use rac_liftSelector:withSignals .


As I said, you could achieve the desired result using subscribeNext :

@weakify(self);
RACSignal *buttonTextSignal = [[[pressedStart map:^id(UIButton *buttonPressed) {
    return [buttonPressed.titleLabel.text isEqualToString:@"Start"] ? @"Stop" : @"Start";
}]
startWith:@"Start"]
subscribeNext:^(NSString *title) {
    @strongify(self);
    [self.startButton setTitle:title forState:UIControlStateNormal];
}];

But as you can see, you should take extra care to avoid a retain cycle, using @weakify and @strongify macros.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM