ReactiveCocoa 3.0 初见(2)

在我的前一篇博客中首次尝试了ReactiveCocoa3.0 (RC3),在那篇博客中提到了新的 Signal 接口和 |> 操作符。 在这篇博客中我将继续我们探索RC3 API的旅程,我将着重讲述信号发生器,还会从整体上谈论一下新 ReactiveCocoa API 的易用性。

如果你已经使用过 ReactiveCocoa,那么你或许已经遇到过热信号和冷信号的概念区分问题,由于这两个概念都是通过同样的 RACSignal 类型表现出来的,所以区分这两个概念一直以来都是个问题。ReactiveCocoa的设计指导文档建议采用不同的命名方式来区分他们,但是这仍然很难以捉摸。

在RC3中使用不同的类型(信号和信号发生器)来代表他们,让热信号和冷信号之间的区别变得更加明显,也让有着细微区别的操作符命名法变得不再细微(你观察一个信号,但是启动一个信号发生者)。在RC3中令人困惑的热信号和冷信号已经彻底消失了。

信号

比较 Signal 和 SignalProducer 最好的方法就是尝试使用它们。

在我的上一篇博客中,我创建了一个简单的信号,它可以每秒发送一个 next 事件:

通过添加几个 println 语句,我们可以观察到什么时候构建信号什么时候发送 next 事件。

如果你创建一个信号的实例:

但是不添加任何一个观察者,由于没有观察者,你将只能看到信号的创建和事件的发送。

信号发生器

信号发生器的初始化使用了同样的模式:

信号和信号发生器在初始化时候的区别不太明显,信号发生器和信号采用同样的范型类型作为参数,一个为next事件的类型,另一个为错误类型。它们也都很典型地在初始化的时候使用了闭包表达式,不过这个闭包表达式并不是返回一个容器变量 RACDisposable ,而是用一个复合的容器实例作为参数。

如果你创建了一个信号发生器,但是没有订阅(没有添加观察者):

你会发现没有 log 被显示在控制台窗口上,定时器也没有启动。

信号发生器是在启动时产生信号的工厂。为了启动定时器,你可以调用定义在信号发生器中的 start 方法或者和 signal API 一样,通过 |> 操作符来使用 start 函数。

在前面的信号例子中,如果你添加了多个观察者,只会启动一个信号定时器。然而对于信号发生器,你每添加一个观察者就会创建一个新的定时器。

信号发生器被用来代表一种过程或者任务,这种过程在启动信号发生器被初始化。而信号代表了一个事件流,不管有没有观察者,事件都会发生。所以,信号发生器适合被用来做网络请求,而信号更适合被应用在处理UI事件流中。

操作信号发生器

用于操作信号发生器的函数都被定义为柯里自由函数,信号也一样。

例如,下面是on操作,它把一些副作用注入到操作流中:

(为了便于阅读,一些函数变量被删除掉了。)

|>操作符也被重载了,使得他可以被应用在信号发生器上:

这么做的结果是,你可以创建信号发生器操作过程渠道:

结合上面那个使用 timer 的信号发生器会产生如下的输出:

在信号发生器上使用信号的操作

如果你仔细观察信号发生器的操作,你会很快意识到它缺少一些“核心”的功能,比如map和filter。

信号发生器使用lift方法,来将指定的信号操作应用到发生器启动时创建的信号上,而不是重复实现signal的操作。

下面是实现方法:

上面的代码使用柯里化的 map 函数创建一个从信号到其他类型的映射,然后映射被转接到信号发生器上。上面代码的结果以log显示如下:

在XCode中,你可以验证原来的信号发生器类型SignalProducer<String, NoError>已经转变成SignalProducer<Int, NoError>类型了:

这很酷是吧!不过它还可以变得更好…

|>操作符还有另外一种重载方式,它能够以更直接的方式在信号上应用操作:

结果是,信号操作可以直接在管道中被执行:

上面的代码展示了信号生产者的on操作可以自由混搭信号的操作,比如map。

关于为什么有些操作被定义在信号上,有些操作被定义在信号生产者上,我还是有些不确定。我们能看到在当前的API中,信号的操作大体上重心在next事件中,允许我们在不同的线程中转换赋值、跳跃、延迟、组合和观察。而信号生产者的API更关心信号的生命周期事件(完成、异常),使用then、flatmap、takeUntil和catch等等类似的操作。

当前情况下,这种方式还有些限制,比如我从一个UITextField中创造一个事件流作为信号,但我不能为它添加flatMap操作,我已经把这个标记为一个问题提了出来。

接口说明

RC3 API包含很多很聪明的概念,它是函数式Swift的一个很好的例子。我个人已经从这个库中学到很多。

另外,RC3还有一些更实用的好处。使用范型意味着信号是强类型的,可以消除全部的runtime错误,最终呈现出更简洁的代码。还有,信号和信号生产者的区别很明显。

然而我不禁会想这些API是不是有点太聪明了!

在我的上一篇关于ReactiveCocoa的文章中,我一直在用一个简单清晰的方式来表明一个主题,那就是向读者展示ReactiveCocoa 的核心概念很容易掌握。我已经收到一些来自读者的很好的反馈,很多人在读完后联系我表示他们理解了。我着实好奇RC3 接口是不是会让那些曾经很艰难地理解ReactiveCocoa的人感到困惑。

以后我会针对一些引起关注的RC3 API进行细致地阐述。

在RC3中,范型的使用很有必要、有很高的价值,而且范型也是一个被充分理解的主流概念。点赞。

RC3的操作使用柯里自由函数,我对它们有几点要说。首先,柯里函数是一个很先进的概念,它们不是你会认为的主流技术。其次,自由函数更不容易被发现,XCode自动补全不会给你任何可用的操作类型的提示。

|>操作符也是一个很前卫的概念,尤其是那个允许你将信号操作应用到信号产生者上的重载用法。挑剔地说,如果你不能理解它是如何工作,也就更不能懂如何将一个简单的map操作应用到信号发生器上。

ReactiveCocoa 3.0目前还是试用版本,我所使用和讲解的api肯定也并不是最终版。这个团队在RC2中做得很好,在RC3中也一直很出色,现在是向他们反馈的时候了。

我已经提出了我对于RC3 API细节的一些问题。一个很有可能的解答方法是复制那些自由函数作为方法在信号和信号发生器上使用(与swift的collection操作方法大体相同),不过,这样的话,最终可能会有些委曲求全。

总结

这是我的关于RC3的第二篇讲述,我还会再写最后一篇博客,主题为构建一个更完整的项目。

1 收藏 评论

关于作者:Yalye

简介还没来得及写 :) 个人主页 · 我的文章 · 17

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部