ReactiveCocoa 中的 distinctUntilChanged 坑
前言
static int a = 0;
[[RACObserve(self, number) distinctUntilChanged] subscribeNext:^(id x) {
a++;
printf("RAC a is %d\n", a);
}];
self.number = @1;
self.number = @2;
这段代码运行之后 a 的值是 3,也就是 subscribeNext 会回调 3 次。
正题
static int a = 0;
[[RACObserve(self, array) distinctUntilChanged] subscribeNext:^(id x) {
a++;
printf("RAC a is %d\n", a);
}];
NSMutableArray *obj = [@[@1, @2] mutableCopy];
NSMutableArray *another = [@[@1, @2, @3] mutableCopy];
self.array = obj;
[another removeLastObject];
self.array = another;
如果换成这样呢?
不同之处在于倒数第二行对 another
进行了修改。
结果是 2,也就是说最后一行的赋值并没有发出消息。
原因在于 distinctUntilChanged 的实现有针对上一个值做比较,如果 isEqual 成立尽管指针不一样消息还是不会发送的。
- (__kindof RACStream *)distinctUntilChanged {
Class class = self.class;
return [[self bind:^{
__block id lastValue = nil;
__block BOOL initial = YES;
return ^(id x, BOOL *stop) {
if (!initial && (lastValue == x || [x isEqual:lastValue])) return [class empty];
initial = NO;
lastValue = x;
return [class return:x];
};
}] setNameWithFormat:@"[%@] -distinctUntilChanged", self.name];
}
但是呢,代码里比较容易出现上面的例子,NSMutableArray 可以换成任意 mutable 的容器以及任意自定义对象,只要你的自定义对象自己实现了 isEqual 就可能造成这种结局。
大多数情况下我们的 isEqual 不会比较指针不一样就直接 return NO。
所以在 RACObserve 对象并希望 distinctUntilChanged 能够工作正常的话要非常小心
- 对象尽量是 “immutable”
- 如果不是 immutable,就要非常小心搭配使用 distinctUntilChanged,如果中途改了对象的 property 很有可能破坏自己的预期
真实世界中很难预防有人写出这样的代码。很大程度上,observe 的动作和改 property 以及更新对象触发回调这三处代码相互隔离非常远,不在一个文件里。写的人摸不清,维护的人更麻烦。
ReactiveCocoa 很难用理由 +1