4

如果我将信号分配给控件的属性:

    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);
            }];

但是后来想分配一个不同RACSignal的 to enabled,我怎么能在这样做之前清除任何现有的?

如果我再次尝试设置它,我会收到如下异常:

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'
4

2 回答 2

29

ReactiveCocoa 哲学的很大一部分是消除状态。状态是可以随着时间而改变的任何东西,它的问题有几个原因:

  1. 你失去了过去的信息。一旦变量被更改,就好像以前的值不存在一样。
  2. 变化可以来自任意数量的地方。很难查看有状态的代码并确切地知道当局部性很差时会发生什么。
  3. 并发和异步使状态管理变得困难,因为您现在必须跨多个执行点协调更改。当存在多个可能相互冲突的参与者时,很难实现确定性。

RAC 不允许多个绑定到同一个属性的原因是它使排序不确定。如果我有两个信号绑定到enabled,哪个优先?我怎么知道哪一个发送了最新的值?

RAC 不允许重新绑定相同属性的原因是它是有状态的事情。就地更改绑定是必要的,并且由于上述所有原因而不好。

相反,使用信号作为一种声明方式来表达随时间的变化。改变属性的一切——现在或将来——都应该在一个信号中表示。

根据您的示例,很难确切知道这些输入是什么,但是假设您想根据 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];

通过这种方式,无论输入实际是什么(用户名/密码或电子邮件),绑定都保持有效,并且我们避免了在运行时改变事物的任何需要。

于 2013-10-30T03:27:19.007 回答
0

除了使用RAC宏,您可以显式使用:

[[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];

对于版本 < 2.0,请-toProperty:onObject:改用

我不确定它是否能处理你的情况,但试试吧,我是新手ReactiveCocoa,但肯定有人可以帮助你,敬请期待:)

于 2013-10-29T09:14:03.770 回答