Remove a ReactiveCocoa signal from a control

戏子无情 提交于 2019-12-08 23:39:54

问题


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'

回答1:


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.




回答2:


Instead of using RAC macro, can you explicitly use :

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

For version < 2.0, use -toProperty:onObject: instead

I'm not sure if it handle your case, but try it, I'm new to ReactiveCocoa, but sure anyone can help you out, just stay tuned :)



来源:https://stackoverflow.com/questions/19650802/remove-a-reactivecocoa-signal-from-a-control

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!