Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove a ReactiveCocoa signal from a control

If I assign a signal to a property of a control:

    RAC(self.loginButton.enabled) = [RACSignal
            combineLatest:@[
                    self.usernameTextField.rac_textSignal,
                    self.passwordTextField.rac_textSignal
            ] reduce:^(NSString* username, NSString* password) {
                return @(username.length > 0 && password.length > 0);
            }];

But then wanted to assign a different RACSignal to enabled, how can I clear any existing one before doing so?

If I try and set it a second time, I get an exception like the following:

2013-10-29 16:54:50.623 myApp[3688:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Signal <RACSignal: 0x975e9e0> name: +combineLatest: (
"<RACSignal: 0x975d600> name: <UITextField: 0x10f2c420> -rac_textSignal",
"<RACSignal: 0x975de30> name: <UITextField: 0x10f306e0> -rac_textSignal"
) reduce: is already bound to key path "self.loginButton.enabled" on object <LoginViewController: 0x10f264e0>, adding signal <RACSignal: 0x9763500> name: +combineLatest: (
"<RACSignal: 0x97624f0> name: <UITextField: 0x10f2c420> -rac_textSignal",
"<RACSignal: 0x97629e0> name: <UITextField: 0x10f306e0> -rac_textSignal"
) reduce: is undefined behavior'
like image 897
Diego Barros Avatar asked Nov 28 '22 11:11

Diego Barros


1 Answers

A big part of ReactiveCocoa's philosophy is the elimination of state. State is anything that can change in-place over time, and it's problematic for a few reasons:

  1. You lose past information. Once a variable has been changed, it's like the previous values never existed.
  2. Changes can come from any number of places. It's hard to look at stateful code and know exactly what will happen when — there's poor locality.
  3. Concurrency and asynchrony makes state management difficult, because you now have to coordinate changes across multiple execution points. Determinism is hard to achieve when there are multiple actors that may conflict with each other.

The reason RAC disallows multiple bindings to the same property is that it makes the ordering nondeterministic. If I have two signals bound to enabled, which one takes precedence? How would I know which one sent the latest value?

The reason RAC disallows rebinding the same property is that it's a stateful thing to do. Changing the binding in-place is imperative, and bad for all the reasons outlined above.

Instead, use signals as a declarative way to express changes over time. Everything that changes a property — now or in the future — should be represented in one signal.

Based on your example, it's hard to know exactly what those inputs would be, but let's say you wanted to use different login text fields based on the value of a UISwitch:

// A signal that automatically updates with the latest value of
// `self.emailLoginSwitch.on`.
RACSignal *emailLoginEnabled = [[[self.emailLoginSwitch
    rac_signalForControlEvents:UIControlEventValueChanged]
    mapReplace:self.emailLoginSwitch]
    map:^(UISwitch *switch) {
        return @(switch.on);
    }];

// Whether the user has entered a valid username and password.
RACSignal *usernameAndPasswordValid = [RACSignal
    combineLatest:@[
        self.usernameTextField.rac_textSignal,
        self.passwordTextField.rac_textSignal
    ] reduce:^(NSString* username, NSString* password) {
        return @(username.length > 0 && password.length > 0);
    }];

// Whether the user has entered a valid email address.
RACSignal *emailValid = [self.emailTextField.rac_textSignal map:^(NSString *email) {
    return @(email.length > 0);
}];

// Uses different conditions for validity depending (ultimately) on the value of
// `self.emailLoginSwitch`.
RAC(self.loginButton, enabled) = [RACSignal
    if:emailLoginEnabled
    then:emailValid
    else:usernameAndPasswordValid];

In this way, the binding remains valid no matter what the inputs actually are (username/password or email), and we've avoided any need to mutate things at runtime.

like image 158
Justin Spahr-Summers Avatar answered Dec 21 '22 22:12

Justin Spahr-Summers