基本变换

基本变换是一组一元变换形式,每次变换是由一个节点出发,经过计算向其下游节点进行传播的,最基本的fork操作就是如此,下面介绍下全部的基本变换形式。

map

map:方法是 EasyReact 相当常用的一个变换方法,它的作用是对上游节点的每一个非空值进行一次计算,并将得到的结果同步的传递给下游节点:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode value:@1];
  2. EZRNode<NSString *> *nodeB = [nodeA map:^NSString *(NSNumber *next){
  3. return next.stringValue;
  4. }];
  5. NSLog(@"%@", nodeB.value); // <- @"1"
  6. nodeA.value = @2;
  7. NSLog(@"%@", nodeB.value); // <- @"2"

有的时候,可能每次 map 的结果和当前传递的值并没有关系,这样我们就可以用mapReplace:来简单处理:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode value:@1];
  2. EZRNode<NSString *> *nodeB = [nodeA mapReplace:@"叮铃,有值啦!"];
  3. [[nodeB listenedBy:self] withBlock:^(NSString *next) {
  4. NSLog(@"%@", next);
  5. }];
  6. nodeA.value = @2;
  7. nodeA.value = @3;
  8. /* 打印如下:
  9. 叮铃,有值啦!
  10. 叮铃,有值啦!
  11. 叮铃,有值啦!
  12. */

需要注意的是mapReplace:创建的边 EZRMapTransform 里面会强持有其入参,注意避免循环引用。

filter

filter:的作用是过滤每个上游的值,将符合条件的值传递给下游:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode value:@1];
  2. EZRNode<NSNumber *> *nodeB = [nodeA filter:^BOOL(NSNumber *next){
  3. return next.integerValue > 5;
  4. }];
  5. NSLog(@"%@", nodeB.value); // <- EZREmpty()
  6. nodeA.value = @6;
  7. NSLog(@"%@", nodeB.value); // <- @6
  8. nodeA.value = @3;
  9. NSLog(@"%@", nodeB.value); // <- @6

对于过滤我们还有两个便捷的方法:ignore:select:,它们的作用是分别过滤相同的和不同的,例如:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode value:@1];
  2. EZRNode<NSNumber *> *nodeB = [nodeA ignore:@1];
  3. EZRNode<NSNumber *> *nodeC = [nodeA select:@1];
  4. [[nodeB listenedBy:self] withBlock:^(NSNumber *next) {
  5. NSLog(@"NodeB 收到 %@", next);
  6. }];
  7. [[nodeC listenedBy:self] withBlock:^(NSNumber *next) {
  8. NSLog(@"NodeC 收到 %@", next);
  9. }];
  10. nodeA.value = @12;
  11. nodeA.value = @1;
  12. nodeA.value = @7;
  13. /* 打印如下:
  14. NodeC 收到 1
  15. NodeB 收到 12
  16. NodeC 收到 1
  17. NodeB 收到 7
  18. */

distinctUntilChanged

distinctUntilChanged方法将一个不传递重复值的变换传递给其衍生节点,例如:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode value:@1];
  2. EZRNode<NSNumber *> *nodeB = [nodeA distinctUntilChanged];
  3. [[nodeB listenedBy:self] withBlock:^(NSNumber *next) {
  4. NSLog(@"收到 %@", next);
  5. }];
  6. nodeA.value = @1;
  7. nodeA.value = @2;
  8. nodeA.value = @2;
  9. nodeA.value = @1;
  10. nodeA.value = @2;
  11. /* 打印如下:
  12. 收到 1
  13. 收到 2
  14. 收到 1
  15. 收到 2
  16. */

throttle

节流描述了这样的一种操作,对于上游的值来说,在一定的时间内如果有新的值则不会传递旧的值,如果等待到指定的时间没有新的值再将之前的值传递到下游。由于传递是异步的,所以阀门操作一般需要指定一个 GCD 的队列来告诉 EasyReact 在哪里进行传递。

一般阀门的操作用于搜索输入这样的需求上用来避免多次请求网络:

  1. EZRMutableNode<NSString *> *inputNode = [EZRMutableNode new];
  2. EZRNode<NSString *> *searchNode = [inputNode throttle:1 queue:dispatch_get_main_queue()]; // <- 单位是秒
  3. [[searchNode listenedBy:self] withBlock:^(NSString *next) {
  4. NSLog(@"想要搜索的是 %@", next);
  5. }];
  6. inputNode.value = @"h";
  7. inputNode.value = @"he";
  8. inputNode.value = @"hel";
  9. inputNode.value = @"hell";
  10. inputNode.value = @"hello";
  11. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
  12. inputNode.value = @"hello ";
  13. inputNode.value = @"hello w";
  14. inputNode.value = @"hello wo";
  15. inputNode.value = @"hello wor";
  16. inputNode.value = @"hello worl";
  17. inputNode.value = @"hello world";
  18. });
  19. /* 打印如下:
  20. 想要搜索的是 hello
  21. 想要搜索的是 hello world
  22. */

大家通常都想要在主队列完成监听,所以throttleOnMainQueue:方法快速的提供了阀门到主队列的能力:

  1. EZRMutableNode<NSString *> *inputNode = [EZRMutableNode new];
  2. EZRNode<NSString *> *searchNode = [inputNode throttleOnMainQueue:1];

等价于

  1. EZRMutableNode<NSString *> *inputNode = [EZRMutableNode new];
  2. EZRNode<NSString *> *searchNode = [inputNode throttle:1 queue:dispatch_get_main_queue()];

skip

跳过操作顾名思义就是跳过前几个值:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
  2. EZRNode<NSNumber *> *nodeB = [nodeA skip:2];
  3. NSLog(@"%@", nodeB.value); // <- EZREmpty()
  4. nodeA.value = @1;
  5. NSLog(@"%@", nodeB.value); // <- EZREmpty()
  6. nodeA.value = @2;
  7. NSLog(@"%@", nodeB.value); // <- EZREmpty()
  8. nodeA.value = @3;
  9. NSLog(@"%@", nodeB.value); // <- @3

take

拿取操作顾名思义就是只拿取前几个值:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
  2. EZRNode<NSNumber *> *nodeB = [nodeA take:2];
  3. NSLog(@"%@", nodeB.value); // <- EZREmpty()
  4. nodeA.value = @1;
  5. NSLog(@"%@", nodeB.value); // <- @1
  6. nodeA.value = @2;
  7. NSLog(@"%@", nodeB.value); // <- @2
  8. nodeA.value = @3;
  9. NSLog(@"%@", nodeB.value); // <- @2

deliverOn

前面提到了在多线程下值的修改和监听是同一线程的,我们也可以使用withBlock:on或者withBlockOnMainQueue在监听的时候进行处理。但是如果在变换的过程中耗时非常长,或者遇到变换中必须在主线程的时候,只在监听上做处理已经满足不了需要了:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
  2. EZRNode<NSNumber *> *nodeB = [nodeA map:^NSNumber *(NSNumber *next) {
  3. [NSThread sleepForTimeInterval:next.doubleValue];
  4. return next;
  5. }];
  6. EZRNode<NSNumber *> *nodeC = [nodeB map:^NSNumber *(NSNumber *next) {
  7. NSAssert([[NSThread currentThread] isMainThread], @"");
  8. return next;
  9. }];
  10. [[nodeC listenedBy:self] withBlock:^(NSNumber * _Nullable next) {
  11. }];
  12. nodeA.value = @(9.0);
  13. // 哇,又要等一会了
  14. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  15. nodeA.value = @3; // 不好,要断言失败了
  16. });

这时deliverOn:deliverOnMainQueue就派上用场了:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
  2. dispatch_queue_t queue = dispatch_queue_create("someQueue", DISPATCH_QUEUE_SERIAL);
  3. EZRNode<NSNumber *> *nodeB = [[nodeA deliverOn:queue] map:^NSNumber *(NSNumber *next) {
  4. [NSThread sleepForTimeInterval:next.doubleValue];
  5. return next;
  6. }];
  7. EZRNode<NSNumber *> *nodeC = [[nodeB deliverOnMainQueue] map:^NSNumber *(NSNumber *next) {
  8. NSAssert([[NSThread currentThread] isMainThread], @"");
  9. return next;
  10. }];
  11. [[nodeC listenedBy:self] withBlock:^(NSNumber * _Nullable next) {
  12. }];
  13. nodeA.value = @(9.0);
  14. // 嗯,不担心
  15. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  16. nodeA.value = @3; // 嗯,不担心
  17. });

delay

延迟操作顾名思义就是推迟一段时间后传递给下游节点,由于传递的时候已经找不到之前上游设置的线程,所以延迟操作需要一个 GCD 的队列来派发传递的任务:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
  2. EZRNode<NSNumber *> *nodeB = [nodeA delay:1 queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
  3. EZRNode<NSNumber *> *nodeC = [nodeA delayOnMainQueue:2];

scan

扫描操作是个稍微复杂一点的操作,它需要传入一个初始值和一个带有两个入参的 block。当上游第一次有值传递过来的时候,会以初始值和上游当前值调用这个 block,block 的返回值就是下游的值并且这个值会被记下来。以后每次上游有值传递过来的时候,都会以上一次记下来的值和上游当前值调用这个 block,以此循环。例如:

  1. EZRMutableNode<NSNumber *> *nodeA = [EZRMutableNode new];
  2. EZRNode<NSMutableArray<NSNumber *> *> *nodeB = [nodeA scanWithStart:[NSMutableArray array] reduce:^NSMutableArray *(NSMutableArray *last, NSNumber *current) {
  3. [last addObject:current];
  4. return last;
  5. }];
  6. [[nodeB listenedBy:self] withBlock:^(NSMutableArray *array) {
  7. NSLog(@"接收到 %@", array);
  8. }];
  9. nodeA.value = @1;
  10. nodeA.value = @2;
  11. nodeA.value = @3;
  12. nodeA.value = @4;
  13. nodeA.value = @5;
  14. /* 打印如下:
  15. 接收到 (
  16. 1
  17. )
  18. 接收到 (
  19. 1,
  20. 2
  21. )
  22. 接收到 (
  23. 1,
  24. 2,
  25. 3
  26. )
  27. 接收到 (
  28. 1,
  29. 2,
  30. 3,
  31. 4
  32. )
  33. 接收到 (
  34. 1,
  35. 2,
  36. 3,
  37. 4,
  38. 5
  39. )
  40. */

其过程如下:

  1. upstream: -----------1-----------2-----------3-----------4-----------5
  2. | | | | |
  3. start: [] | | | | |
  4. downstream: ---------[1]-------→[1,2]-----→[1,2,3]---→[1,2,3,4]-→[1,2,3,4,5]