Reactive Cocoa 技术整理

1.Reactive Cocoa解决了什么问题

  1. 传统iOS开发过程中,状态以及状态之间依赖过多的问题
  2. 传统MVC架构的问题:Controller比较复杂,可测试性差
  3. 提供统一的消息传递机制

2.基本概念

一个很好的比喻

可以把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。可以在水龙头上加一个过滤嘴(filter),不符合的不让通过,也可以加一个改动装置,把球改变成符合自己的需求(map)。也可以把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。

信号(Signal):信号就是流,RAC中所有的信号都是RACStream的子类。信号有冷热之分,信号也有中继、改变等操作。信号是让订阅者去订阅,然后触发对应事件。

  • 冷信号(Cold):是被动的信号,只有当你订阅的时候,它才会发布消息。只能一对一,当有不同的订阅者,消息会从新完整发送。
  • 热信号(Hot):是主动的信号,即使你没有订阅事件,它仍然会时刻推送。可以有多个订阅者,是一对多,信号可以与订阅者共享信息。

订阅者(Subscriber):负责接收信号执行操作,都必须实现RACSubscriber协议。订阅者之间的关系原则上是平等的,但实际操作上也是有先后顺序,而且还有副作用的情况需要考虑。

事件类型:信号(RACSignal)发送给订阅者(Subscriber)的事件类型有三种,分别是next、error、completed。一个signal在因error终止或者完成前,可以发送任意数量的next事件。三种事件对应函数如下:

subscribeNext:^(id x)

subscribeError:^(NSError *error)

subscribeCompleted:^

有些地方需要注意下,比如把signal作为local变量时,如果没有被subscribe,那么方法执行完后,该变量会被dealloc。但如果signal有被subscribe,那么subscriber会持有该signal,直到signal sendCompleted或sendError时,才会解除持有关系,signal才会被dealloc。

副作用(Side Effect):在每一次订阅的是时候都会触发对应的操作,导致订阅者收到信号的时候,结果会和其他订阅者不同。添加附加操作,如doNext:因为它是附加操作,并不改变事件本身。

生命周期:一个信号的生命周期是由任意个 next 事件和一个 error 事件或一个 completed事件组成的。

Reactive结构图

3. RACStream

RACStream结构图
RACStream 是一个抽象类,通常情况下,我们并不会去实例化它,而是直接使用它的两个子类 RACSignal 和 RACSequence 。

RACSignal

RACSignal 并非只有一个类,事实上,它的一系列功能是通过类簇来实现的。

  • RACEmptySignal :空信号,用来实现 RACSignal 的 +empty 方法;
  • RACReturnSignal :一元信号,用来实现 RACSignal 的 +return: 方法;
  • RACDynamicSignal :动态信号,使用一个 block 来实现订阅行为,我们在使用 RACSignal 的 +createSignal: 方法时创建的就是该类的实例;
  • RACErrorSignal :错误信号,用来实现 RACSignal 的 +error: 方法;
  • RACChannelTerminal :通道终端,代表 RACChannel 的一个终端,用来实现双向绑定。

对于 RACSignal 类簇来说,最核心的方法莫过于 -subscribe: 了,这个方法封装了订阅者对信号源的一次订阅过程,它是<font color = red>订阅者与信号源产生联系的唯一入口</font>。

RACSequence

代表的是一个不可变的值的序列,从严格意义上讲,RACSequence 并不能算作是信号源,因为它并不能像 RACSignal 那样,可以被订阅者订阅,但是它与 RACSignal 之间可以非常方便地进行转换。RACSequence 存在的最大意义就是为了简化 Objective-C 中的集合操作。

从理论上说,一个 RACSequence 由两部分组成:

  • head :指的是序列中的第一个对象,如果序列为空,则为 nil ;
  • tail :指的是序列中除第一个对象外的其它所有对象,同样的,如果序列为空,则为 nil 。

RACSequence 的一系列功能也是通过类簇来实现的,它共有九个用来实现不同功能的私有子类:

  • RACUnarySequence :一元序列,用来实现 RACSequence 的 +return: 方法;
  • RACIndexSetSequence :用来遍历索引集;
  • RACEmptySequence :空序列,用来实现 RACSequence 的 +empty 方法;
  • RACDynamicSequence :动态序列,使用 blocks 来动态地实现一个序列;
  • RACSignalSequence :用来遍历信号中的值;
  • RACArraySequence :用来遍历数组中的元素;
  • RACEagerSequence :非懒计算的序列,在初始化时立即计算所有的值;
  • RACStringSequence :用来遍历字符串中的字符;
  • RACTupleSequence :用来遍历元组中的元素。

4.RACCommand

RACCommand:代表着与交互后即将执行的一段流程。通常这个交互是UI层级的,比如你点击个Button。RACCommand可以方便的将Button与enable状态进行绑定,也就是当enable为NO的时候,这个RACCommand将不会执行。RACCommand还有一个常见的策略:allowsConcurrentExecution,默认为NO,也就是是当你这个command正在执行的话,你多次点击Button是没有用的。创建一个RACCommand的返回值是一个Signal,这个Signal会返回next或者complete或者error。它有几个比较重要的属性:executionSignals / errors / executing。

  • executionSignals是signal of signals,如果直接subscribe的话会得到一个signal,而不是我们想要的value,所以一般会配合switchToLatest。
  • errors。跟正常的signal不一样,RACCommand的错误不是通过sendError来实现的,而是通过errors属性传递出来的。
  • executing表示该command当前是否正在执行。

5.Reactive Cocoa的使用

combineLatest(聚合信号):把不同信号产生的最新的值聚合在一起,并生成一个新的信号。每次这两个源信号的任何一个产生新值时,reduce block都会执行,block的返回值会发给下一个信号。

Map(转换):Map是接收到信号后,对信号的返回值处理,然后根据需要改变信号的返回值,继续传递该信号。

Injecting effects(注入效果):对信号做一些处理 -doNext: -doError: -doCompleted:。

Filtering(过滤):过滤不符合需求的信号。

FlattenMap(平铺拓扑):收到信号后,对信号返回值进行处理后,返回一个新的信号,可以改变信号的返回值。

Flatten(平铺):当订阅者的数量达到最大当前订阅数的时候,合并接收者发送的各种信号到一个平铺信号中。新的信号会像其他信号一样被排队、订阅。

Concat(拼接):一个信号拼接到另一个信号后面

····

6.RACSignal (Debugging)

    /// Logs all events that the receiver sends.
- (RACSignal *)logAll;

/// Logs each `next` that the receiver sends.
- (RACSignal *)logNext;

/// Logs any error that the receiver sends.
- (RACSignal *)logError;

/// Logs any `completed` event that the receiver sends.
- (RACSignal *)logCompleted;

调试辅助函数,可以观察信号的关闭传递等。

附:

函数式编程(Functional Programming):使用高阶函数,例如函数用其他函数作为参数。

响应式编程(Reactive Programming):关注于数据流和变化传播。响应式编程可以避免使用追踪瞬时状态的的实例变量。

RAC()宏:等价于产生信号、传递信号,以及订阅信号。针对某个对象的一个属性进行绑定,信号返回值是对属性的赋值

取消信号:在一个completed或者error事件之后,订阅会自动移除;也可以通过RACDisposable 手动移除订阅。

双向绑定:两边任何一边变化,都会对另一边产生影响。eg. RACChannelTo(self, blockShowText) = RACChannelTo(self.viewModel, blockShowText);

FlattenMap和Map的区别:Map是通过FlattenMap来实现的,Map只是改变分发的值。而FlattenMap不仅可以获得上一个信号的值,还能激活下一个信号,形成这种链式反应。Map是FlattenMap在流中信号数量为1,而且信号值是block的返回值的特殊情况。Map是改变一个信号的返回值,FlattenMap是创建一个新的信号。

MVVM概述

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容