有句话说的好,“RP 不是万能的,没有 RP 却是万万不能的”。在 iOS 开发中,RP(响应式编程) 用得越来越多,其中我们最常用的响应式开发框架就是 ReactiveCocoa(俗称 RAC)。无可否认,RAC 的功能确实非常强大,可以用它完美替代 KVO、delegate、通知、UI 事件处理机制、定时器等一切对象通信机制,以至于很多同学把 RAC 当成了万应灵药。
那么 RAC 是万能的吗?程序员可以在任何地方使用它吗?
答案当然是不。与此相反,RAC 一旦使用不当,它就会变成致命的毒药。接下来,我们将列举两个 RAC 不适用的例子。
rac_textSignal 是对 UITextField/UITextView 委托模型的替代,但这种替代是破坏性的。
也就是说,一旦你使用了 rac_textSignal,你将无法在 UITextField/UITextView 上使用委托模式。
原因就在于:
这就导致了一种潜在的问题。考虑这样一种场景。如果你需要限制 TextField 中用户能够输入的字数。通常我们会在 rac_textSignal 订阅块中,对输入的字符数进行判断,一旦它超过限制,我们就对字符串进行截断。这很简单,简单到可能只需要一行代码。但这样就会在订阅块中直接修改到 textfield 的 text 属性,由于代码修改 text 属性导致 textfiel 的 KVO 事件发生,那么又会触发 rac_textSingal 信号。如果你没有对修改 text 的语句进行条件判断,那么这就会导致死循环发生,app 崩溃。
如果在中文输入法打开的情况下,还会导致重复输入候选词条的情况。比如你输入"df",文本框中会出现多次重复的"dfdfdfdf"。
在调试模式下,rac_textSignal 还会导致 delete 字符自动重复的情况。即按一下 delete 键,发送两个 delete 字符。
rac_textSignal 用在复用的 cell 时问题更大。
有时候问为了监听 cell 中 textfield 的文字改变,我们会在 cellForItemAtIndex 方法中使用这样的代码:
TextFieldCell* cell = [self.collectionContext dequeueReusableCellOfClass:TextFieldCell.class forSectionController:self atIndex:index];
cell.tfText.text= dev.management;
[[[cell.tfText rac\_textSignal] skip:1] subscribeNext:^(NSString * _Nullable x) {
self.device.management = x;
}];
这样的问题在于,由于 cell 是复用的,当 cell 复用时,rac_textSignal 会被重复订阅。当你从缓存中复用一个 cell 时,这个 cell 的 rac_textSignal 很可能已经被订阅过了。这个时候你再次订阅 rac_textSignal 信号,就重复订阅了。导致用户在当前 cell 的文本框中输入值后,重复调用订阅块,即可能改变了多个值。比如,明明用户在输入姓名时输入的是 a,但莫名其妙不仅仅是用户的姓名被修改为 a 了,用户的邮箱、手机号都被修改为 a 了。
这个问题一般很难排查,因为 cell 很可能不被复用,或者什么时候会复用一个 cell 我们也无从知道,cell 的复用机制位于 UITableView/UICollectionView 的“黑盒子”中。
更糟糕的是,RAC 本身无法解决这个问题。你可能想在订阅一个 rac_textSignal 信号前将同一个 textfield 上的 rac_textSignal 信号停止,或者取消其它订阅者,但 RAC 本身没有提供这样的机制。只有在订阅时保持住一个 RACDisposable,然后在每次订阅前调用 dispose 方法。
// 有多少个 textfield 就要有多少个 RACDisposal 属性
@property (strong, nonatomic) RACDisposable* disposable1;
@property (strong, nonatomic) RACDisposable* disposable2;
@property (strong, nonatomic) RACDisposable* disposable3;
... ...
// 在订阅之前 dispose,有多少个 textfield 就要 dispose 多少次
[_disposable1 dispose];
_disposable1 = [[cell.tfText rac\_textSignal] subscribeNext:^(NSString * _Nullable x) {
self.device.management = x;
}];
... ...
这个方式和另外一种方式一样,假设每个 cell 上都有一个 textfield,那么有多少个 cell 就得声明多少个属性,如果 cell 的个数不固定,这种方案不是很美妙啊。
另外一种方式就是保持住每一个 cell,即有多少 cell 就声明多少属性,每个 cell 属性都使用懒加载的方式,即只有在第一次初始化时订阅 rac_textSignal,这样就不会重复订阅。
当然最完美的解决办法就是不使用 rac_textSignal,而是用 UITextFieldDelegate,同时通过 index 来识别这是哪个 textfield:
cell.tfText.tag = index;
cell.tfText.delegate = self;
......
-(void)textFieldDidEndEditing:(UITextField *)textField{
switch (textField.tag) {
case 5:{// 管理部门
_device.management = textField.text;
break;
}
case 6:{// 管理人员
_device.managementUser = textField.text;
break;
}
case 7:{// 负责人员
_device.liableUser = textField.text;
break;
}
default:
break;
}
}