基本变换
基本变换是一组一元变换形式,每次变换是由一个节点出发,经过计算向其下游节点进行传播的,最基本的fork
操作就是如此,下面介绍下全部的基本变换形式。
map
map:
方法是 EasyReact 相当常用的一个变换方法,它的作用是对上游节点的每一个非空值进行一次计算,并将得到的结果同步的传递给下游节点:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode value:@1];
EZRNode<NSString *> *nodeB = [nodeA map:^NSString *(NSNumber *next){
return next.stringValue;
}];
NSLog(@"%@", nodeB.value); // <- @"1"
nodeA.value = @2;
NSLog(@"%@", nodeB.value); // <- @"2"
有的时候,可能每次 map 的结果和当前传递的值并没有关系,这样我们就可以用mapReplace:
来简单处理:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode value:@1];
EZRNode<NSString *> *nodeB = [nodeA mapReplace:@"叮铃,有值啦!"];
[[nodeB listenedBy:self] withBlock:^(NSString *next) {
NSLog(@"%@", next);
}];
nodeA.value = @2;
nodeA.value = @3;
/* 打印如下:
叮铃,有值啦!
叮铃,有值啦!
叮铃,有值啦!
*/
需要注意的是mapReplace:
创建的边 EZRMapTransform 里面会强持有其入参,注意避免循环引用。
filter
filter:
的作用是过滤每个上游的值,将符合条件的值传递给下游:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode value:@1];
EZRNode<NSNumber *> *nodeB = [nodeA filter:^BOOL(NSNumber *next){
return next.integerValue > 5;
}];
NSLog(@"%@", nodeB.value); // <- EZREmpty()
nodeA.value = @6;
NSLog(@"%@", nodeB.value); // <- @6
nodeA.value = @3;
NSLog(@"%@", nodeB.value); // <- @6
对于过滤我们还有两个便捷的方法:ignore:
和select:
,它们的作用是分别过滤相同的和不同的,例如:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode value:@1];
EZRNode<NSNumber *> *nodeB = [nodeA ignore:@1];
EZRNode<NSNumber *> *nodeC = [nodeA select:@1];
[[nodeB listenedBy:self] withBlock:^(NSNumber *next) {
NSLog(@"NodeB 收到 %@", next);
}];
[[nodeC listenedBy:self] withBlock:^(NSNumber *next) {
NSLog(@"NodeC 收到 %@", next);
}];
nodeA.value = @12;
nodeA.value = @1;
nodeA.value = @7;
/* 打印如下:
NodeC 收到 1
NodeB 收到 12
NodeC 收到 1
NodeB 收到 7
*/
distinctUntilChanged
distinctUntilChanged
方法将一个不传递重复值的变换传递给其衍生节点,例如:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode value:@1];
EZRNode<NSNumber *> *nodeB = [nodeA distinctUntilChanged];
[[nodeB listenedBy:self] withBlock:^(NSNumber *next) {
NSLog(@"收到 %@", next);
}];
nodeA.value = @1;
nodeA.value = @2;
nodeA.value = @2;
nodeA.value = @1;
nodeA.value = @2;
/* 打印如下:
收到 1
收到 2
收到 1
收到 2
*/
throttle
节流描述了这样的一种操作,对于上游的值来说,在一定的时间内如果有新的值则不会传递旧的值,如果等待到指定的时间没有新的值再将之前的值传递到下游。由于传递是异步的,所以阀门操作一般需要指定一个 GCD 的队列来告诉 EasyReact 在哪里进行传递。
一般阀门的操作用于搜索输入这样的需求上用来避免多次请求网络:
EZRMutableNode<NSString *> *inputNode = [EZRMutableNode new];
EZRNode<NSString *> *searchNode = [inputNode throttle:1 queue:dispatch_get_main_queue()]; // <- 单位是秒
[[searchNode listenedBy:self] withBlock:^(NSString *next) {
NSLog(@"想要搜索的是 %@", next);
}];
inputNode.value = @"h";
inputNode.value = @"he";
inputNode.value = @"hel";
inputNode.value = @"hell";
inputNode.value = @"hello";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
inputNode.value = @"hello ";
inputNode.value = @"hello w";
inputNode.value = @"hello wo";
inputNode.value = @"hello wor";
inputNode.value = @"hello worl";
inputNode.value = @"hello world";
});
/* 打印如下:
想要搜索的是 hello
想要搜索的是 hello world
*/
大家通常都想要在主队列完成监听,所以throttleOnMainQueue:
方法快速的提供了阀门到主队列的能力:
EZRMutableNode<NSString *> *inputNode = [EZRMutableNode new];
EZRNode<NSString *> *searchNode = [inputNode throttleOnMainQueue:1];
等价于
EZRMutableNode<NSString *> *inputNode = [EZRMutableNode new];
EZRNode<NSString *> *searchNode = [inputNode throttle:1 queue:dispatch_get_main_queue()];
skip
跳过操作顾名思义就是跳过前几个值:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
EZRNode<NSNumber *> *nodeB = [nodeA skip:2];
NSLog(@"%@", nodeB.value); // <- EZREmpty()
nodeA.value = @1;
NSLog(@"%@", nodeB.value); // <- EZREmpty()
nodeA.value = @2;
NSLog(@"%@", nodeB.value); // <- EZREmpty()
nodeA.value = @3;
NSLog(@"%@", nodeB.value); // <- @3
take
拿取操作顾名思义就是只拿取前几个值:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
EZRNode<NSNumber *> *nodeB = [nodeA take:2];
NSLog(@"%@", nodeB.value); // <- EZREmpty()
nodeA.value = @1;
NSLog(@"%@", nodeB.value); // <- @1
nodeA.value = @2;
NSLog(@"%@", nodeB.value); // <- @2
nodeA.value = @3;
NSLog(@"%@", nodeB.value); // <- @2
deliverOn
前面提到了在多线程下值的修改和监听是同一线程的,我们也可以使用withBlock:on
或者withBlockOnMainQueue
在监听的时候进行处理。但是如果在变换的过程中耗时非常长,或者遇到变换中必须在主线程的时候,只在监听上做处理已经满足不了需要了:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
EZRNode<NSNumber *> *nodeB = [nodeA map:^NSNumber *(NSNumber *next) {
[NSThread sleepForTimeInterval:next.doubleValue];
return next;
}];
EZRNode<NSNumber *> *nodeC = [nodeB map:^NSNumber *(NSNumber *next) {
NSAssert([[NSThread currentThread] isMainThread], @"");
return next;
}];
[[nodeC listenedBy:self] withBlock:^(NSNumber * _Nullable next) {
}];
nodeA.value = @(9.0);
// 哇,又要等一会了
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
nodeA.value = @3; // 不好,要断言失败了
});
这时deliverOn:
和deliverOnMainQueue
就派上用场了:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
dispatch_queue_t queue = dispatch_queue_create("someQueue", DISPATCH_QUEUE_SERIAL);
EZRNode<NSNumber *> *nodeB = [[nodeA deliverOn:queue] map:^NSNumber *(NSNumber *next) {
[NSThread sleepForTimeInterval:next.doubleValue];
return next;
}];
EZRNode<NSNumber *> *nodeC = [[nodeB deliverOnMainQueue] map:^NSNumber *(NSNumber *next) {
NSAssert([[NSThread currentThread] isMainThread], @"");
return next;
}];
[[nodeC listenedBy:self] withBlock:^(NSNumber * _Nullable next) {
}];
nodeA.value = @(9.0);
// 嗯,不担心
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
nodeA.value = @3; // 嗯,不担心
});
delay
延迟操作顾名思义就是推迟一段时间后传递给下游节点,由于传递的时候已经找不到之前上游设置的线程,所以延迟操作需要一个 GCD 的队列来派发传递的任务:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
EZRNode<NSNumber *> *nodeB = [nodeA delay:1 queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
EZRNode<NSNumber *> *nodeC = [nodeA delayOnMainQueue:2];
scan
扫描操作是个稍微复杂一点的操作,它需要传入一个初始值和一个带有两个入参的 block。当上游第一次有值传递过来的时候,会以初始值和上游当前值调用这个 block,block 的返回值就是下游的值并且这个值会被记下来。以后每次上游有值传递过来的时候,都会以上一次记下来的值和上游当前值调用这个 block,以此循环。例如:
EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
EZRNode<NSMutableArray<NSNumber *> *> *nodeB = [nodeA scanWithStart:[NSMutableArray array] reduce:^NSMutableArray *(NSMutableArray *last, NSNumber *current) {
[last addObject:current];
return last;
}];
[[nodeB listenedBy:self] withBlock:^(NSMutableArray *array) {
NSLog(@"接收到 %@", array);
}];
nodeA.value = @1;
nodeA.value = @2;
nodeA.value = @3;
nodeA.value = @4;
nodeA.value = @5;
/* 打印如下:
接收到 (
1
)
接收到 (
1,
2
)
接收到 (
1,
2,
3
)
接收到 (
1,
2,
3,
4
)
接收到 (
1,
2,
3,
4,
5
)
*/
其过程如下:
upstream: -----------1-----------2-----------3-----------4-----------5
| | | | |
start: [] | | | | |
↘ ↓ ↓ ↓ ↓ ↓
downstream: ---------[1]-------→[1,2]-----→[1,2,3]---→[1,2,3,4]-→[1,2,3,4,5]