框架概述

本文档描述了 EasyReact 框架的不同组件的高层描述,并试图解释它们如何协同工作。你可以把本文档作为一个学习起点,并找到更多相关的具体文档。

要寻找例子或者深入理解如何使用 EasyReact,请参考 READMEBasicOperators

理论基础

本框架的理论基础是图论中的有向有环图。由节点构成了数据的连接,边的方向表达了流动方向。

节点

我们用 EZRNode\ 类来表示一个节点。所有 T 类的实例都可以被包装到一个节点中,节点中含有一个T value属性,这个值属性用于存放实例。

值得注意的是,节点值也可能是 nil,这使得我们需要区分一个节点到底是存放了 nil 值还是什么都没有存放。所以当我们创建一个新的节点而不给它值时,此节点的值为 EZREmpty 类型的EZREmpty.empty,它用来表示值是“空”的这个概念。

我们可以随时通过- (id)value这个方法来获得节点当前时刻的值,也可以用点语法someNode.value来表示。更多的时候,我们将节点与节点间连接起来,这样一个节点的值就会随着另一个节点的值改变而改变,这种依赖的形式也就是响应式编程的基本思想。

EZRNode\ 类的实例是不可以主动触发变动的,如果想得到一个随意改变值的节点,我们需要 EZRNode\ 的子类 EZRMutableNode\。 EZRMutableNode\- (void)setValue:(nullable T)newValue这个方法用来改变值,也可以用点语法someNode.value = newValue来表示。另外我们也可以给数据传递附加一个上下文对象,使用- (void)setValue:(nullable T)value context:(nullable id)context方法。整个传递的过程中这个上下文对象会传递给每个节点和边。

监听者

监听者在本框架中并没有特定的类型,任意对象都可以作为监听者。监听者是一种特殊的节点,它在整个有向图中是没有下游边的端点。

监听者代表了关心数据变化的主体,也用来维持整个响应链条的内存管理。当一个监听者被销毁的时候,会向上检查所有没有监听者的节点并将之释放(引用计数减一,如果一个节点被一个以上对象强持有则不会销毁)。

我们用 EZREdge 协议来表示有向边。每个id<EZREdge>都有 from 和 to 两个属性来指定来源与去向。from 到 to 的方向也就是数据的流动方向。

接收者

我们用 EZRNextReceiver 协议来表示接收者,它表示可以不断接收新值的对象。EZRNextReceiver 协议有个非常重要的- (void)next:(nullable id)value from:(nonnull EZRSenderList *)senderList context:(nullable id)context方法,调用这个方法就可以向这个接收者发送新的值。

另外,EZRNextReceiver 协议还有个- (void)emptyFrom:(nonnull EZRSenderList *)senderList context:(nullable id)context方法,调用这个方法就可以向这个接收者通知一个空值。

变换

EZREdge 协议有一个子协议 EZRTransformEdge,它表示数据流动中的变换,同时它也满足 EZRNextReceiver 协议。EZRTransformEdge 协议的 from 和 to 属性一定指向一个节点。

每当来源节点执行setValue:或者setValue:context:的时候,就会调用所有的相连的下游边(from 指向该节点的边)的- (void)next:(nullable id)value from:(nonnull EZRSenderList *)senderList context:(nullable id)context方法或- (void)emptyFrom:(nonnull EZRSenderList *)senderList context:(nullable id)context方法。

EZRTransform 是 EZRTransformEdge 协议的一个默认实现类,它帮助我们实现了 from 和 to 属性的 setter 和 getter,并且实现了 EZRNextReceiver 协议的- (void)next:(nullable id)value from:(nonnull EZRSenderList *)senderList context:(nullable id)context方法和- (void)emptyFrom:(nonnull EZRSenderList *)senderList context:(nullable id)context方法。它的变换规则是将每一个 from 节点通过- (void)next:(nullable id)value from:(nonnull EZRSenderList *)senderList context:(nullable id)context方法传过来的值和上下文对象都原封不动的传递给 to 节点,并忽略- (void)emptyFrom:(nonnull EZRSenderList *)senderList context:(nullable id)context方法。

想要定制自己的边只需要继承 EZRTransform 类并覆盖- (void)next:(nullable id)value from:(nonnull EZRSenderList *)senderList context:(nullable id)context方法就可以了。如果需要向 to 节点传递,请务必使用父类的next:from:context方法,将入参 value 改变为想要传递的值,同时保持入参 from 和 context 不变。也可以覆盖- (void)emptyFrom:(nonnull EZRSenderList *)senderList context:(nullable id)context实现空值的传递逻辑。详细的内容可以参考源代码中 EasyReact/Classes/Core/NodeTransforms 中实现的默认边。

上游与下游

数据的流动是有方向的。因此数据的提供者叫做上游,数据的需求者叫做下游。上游和下游是一个逻辑上的概念。

为了方便遍历和深度、广度搜索,每个节点都拥有upstreamNodesdownstreamNodesupstreamTransformsdownstreamTransforms等聚合属性用来获得上下游的边和节点。

另外,一个节点和另一个节点是可能存在多条边的,所以我们可以通过- (NSArray<id<EZRTransformEdge>> *)upstreamTransformsFromNode:(EZRNode *)from方法找到连接到 from 节点的所有的上游变换。相应的,- (NSArray<id<EZRTransformEdge>> *)downstreamTransformsToNode:(EZRNode *)to方法找到连接到 to 节点的所有的下游变换。

监听边

EZREdge 有另外一个子协议 EZRListenEdge,它表示数据流动中的监听行为,同时它也满足 EZRNextReceiver 协议。EZRListenEdge 协议的 from 属性一定指向一个节点,它的 to 属性指向一个 监听者

EZRListen 是 EZRListenEdge 协议的一个默认实现类,它帮助我们实现了 from 和 to 属性的 setter 和 getter,并且实现了 EZRNextReceiver 协议的- (void)next:(nullable id)value from:(nonnull EZRSenderList *)senderList context:(nullable id)context方法和- (void)emptyFrom:(nonnull EZRSenderList *)senderList context:(nullable id)context方法。默认的两个方法并没有做什么,你可以子类化并且覆盖这个方法。

EZRBlockListen 和 EZRDeliveredListen 是 EZRListen 类的两个子类,可以方便的指定 block 和 GCD 的 queue 来完成监听,通常情况下可以满足我们的需要。

连接和数据流动

响应式编程的过程就是描述节点与节点、节点与监听者的关系,最终构成响应图的过程。

本框架中所有的节点关系都是可以后期改变的,所以可以随时通过遍历和修改的方式来改变现有的响应图。

一种特殊情况是节点环。当节点相互成为下游的时候便形成了节点环,例如 a —> b —> c —> a 形成了一个 a, b, c 的三角形节点环。节点环的数据流动是不会循环的,我们通过传递方法- (void)next:(nullable id)value from:(nonnull EZRSenderList *)senderList context:(nullable id)context的参数senderList来避免循环。所以当 b 改变的时候,c 会随之改变,然后再改变 a,a 在数据传递的时候发现 senderList 中已经包含了 b 所以不会继续向 b 传递数据,从而终止了循环。