使用ReactiveCocoa实现iOS平台响应式编程

ReactiveCocoa和响应式编程

在说ReactiveCocoa之前,先要介绍一下FRP(Functional Reactive Programming,响应式编程),在维基百科中有这样一个样例介绍:

在命令式编程环境中,a = b + c 表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。

Excel就是响应式编程的一个样例。单元格能够包括字面值或类似”=B1+C1″的公式,而包括公式的单元格的值会根据其它单元格的值的变化而变化 。

而ReactiveCocoa简称RAC,就是基于响应式编程思想的Objective-C实践,它是Github的一个开源项目,你能够在这里找到它。

关于FRP和ReactiveCocoa能够去看leezhong的这篇blog,图文并茂,讲的非常好。

ReactiveCocoa框架概览

先来看一下leezhong再博文中提到的比喻,让你对有个ReactiveCocoa非常好的理解:

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

以下我来逐一介绍ReactiveCocoa框架的每一个组件

Streams

Streams 表现为RACStream类,能够看做是水管里面流动的一系列玻璃球,它们有顺序的依次通过,在第一个玻璃球没有到达之前,你没法获得第二个玻璃球。
RACStream描写叙述的就是这样的线性流动玻璃球的形态,比較抽象,它本身的使用意义并不非常大,通常会以signals或者sequences等这些更高层次的表现形态取代。

Signals

Signals 表现为RACSignal类,就是前面提到水龙头,ReactiveCocoa的核心概念就是Signal,它一般表示未来要到达的值,想象玻璃球一个个从水龙头里出来,仅仅有了接收方(subscriber)才干获取到这些玻璃球(value)。

Signal会发送以下三种事件给它的接受方(subscriber),想象成水龙头有个指示灯来汇报它的工作状态,接受方通过-subscribeNext:error:completed:对不同事件作出对应反应

  • next 从水龙头里流出的新玻璃球(value)
  • error 获取新的玻璃球发生了错误,一般要发送一个NSError对象,表明哪里错了
  • completed 所有玻璃球已经顺利抵达,没有很多其它的玻璃球增加了

一个生命周期的Signal能够发送随意多个“next”事件,和一个“error”或者“completed”事件(当然“error”和“completed”仅仅可能出现一种)

Subjects

subjects 表现为RACSubject类,能够觉得是“可变的(mutable)”信号/自己定义信号,它是嫁接非RAC代码到Signals世界的桥梁,非常实用。嗯。。。 这样讲还是非常抽象,举个样例吧:

1
2
3

RACSubject *letters = [RACSubject subject];
RACSignal *signal = [letters sendNext:@"a"];

能够看到@"a"仅仅是一个NSString对象,要想在水管里顺利流动,就要借RACSubject的力。

Commands

command 表现为RACCommand类,偷个懒直接举个样例吧,比方一个简单的注冊界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

    RACSignal *formValid = [RACSignal
        combineLatest:@[
            self.userNameField.rac_textSignal,
            self.emailField.rac_textSignal,
        ]
        reduce:^(NSString *userName, NSString *email) {
            return @(userName.length > 0
                    && email.length > 0);
        }];
   RACCommand *createAccountCommand = [RACCommand commandWithCanExecuteSignal:formValid];
   RACSignal *networkResults = [[[createAccountCommand
       addSignalBlock:^RACSignal *(id value) {
           //... 网络交互代码
       }]
       switchToLatest]
       deliverOn:[RACScheduler mainThreadScheduler]];
   // 绑定创建button的 UI state 和点击事件
    [[self.createButton rac_signalForControlEvents:UIControlEventTouchUpInside] executeCommand:createAccountCommand];

Sequences

sequence 表现为RACSequence类,能够简单看做是RAC世界的NSArray,RAC添加了-rac_sequence方法,能够使诸如NSArray这些集合类(collection classes)直接转换为RACSequence来使用。

Schedulers

scheduler 表现为RACScheduler类,类似于GCD,but schedulers support cancellationbut schedulers support cancellation, and always execute serially.

ReactiveCocoa的简单使用

实践出真知,以下就举一些简单的样例,一起看看RAC的使用

Subscription

接收 -subscribeNext: -subscribeError: -subscribeCompleted:

1
2
3
4
5
6
7

RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// 依次输出 A B C D…
[letters subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];

Injecting effects

注入效果 -doNext: -doError: -doCompleted:,看以下凝视应该就明确了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

__block unsigned subscriptions = 0;
RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
    subscriptions++;
    [subscriber sendCompleted];
    return nil;
}];
// 不会输出不论什么东西
loggingSignal = [loggingSignal doCompleted:^{
    NSLog(@"about to complete subscription %u", subscriptions);
}];
// 输出:
// about to complete subscription 1
// subscription 1
[loggingSignal subscribeCompleted:^{
    NSLog(@"subscription %u", subscriptions);
}];

Mapping

-map: 映射,能够看做对玻璃球的变换、又一次组装

1
2
3
4
5
6
7

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
// Contains: AA BB CC DD EE FF GG HH II
RACSequence *mapped = [letters map:^(NSString *value) {
    return [value stringByAppendingString:value];
}];

Filtering

-filter: 过滤,不符合要求的玻璃球不同意通过

1
2
3
4
5
6
7

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: 2 4 6 8
RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) {
    return (value.intValue % 2) == 0;
}];

Concatenating

-concat: 把一个水管拼接到还有一个水管之后

1
2
3
4
5
6

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *concatenated = [letters concat:numbers];

Flattening

-flatten:

Sequences are concatenated

1
2
3
4
5
6
7

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *sequenceOfSequences = @[ letters, numbers ].rac_sequence;
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *flattened = [sequenceOfSequences flatten];

Signals are merged (merge能够理解成把几个水管的龙头合并成一个,哪个水管中的玻璃球哪个先到先吐哪个玻璃球)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
    [subscriber sendNext:letters];
    [subscriber sendNext:numbers];
    [subscriber sendCompleted];
    return nil;
}];
RACSignal *flattened = [signalOfSignals flatten];
// Outputs: A 1 B C 2
[flattened subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];

Mapping and flattening

-flattenMap: 先 map 再 flatten

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
RACSequence *extended = [numbers flattenMap:^(NSString *num) {
    return @[ num, num ].rac_sequence;
}];
// Contains: 1_ 3_ 5_ 7_ 9_
RACSequence *edited = [numbers flattenMap:^(NSString *num) {
    if (num.intValue % 2 == 0) {
        return [RACSequence empty];
    } else {
        NSString *newNum = [num stringByAppendingString:@"_"];
        return [RACSequence return:newNum];
    }
}];
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
[[letters
    flattenMap:^(NSString *letter) {
        return [database saveEntriesForLetter:letter];
    }]
    subscribeCompleted:^{
        NSLog(@"All database entries saved successfully.");
    }];

Sequencing

-then:

1
2
3
4
5
6
7
8
9
10
11
12
13

RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// 新水龙头仅仅包括: 1 2 3 4 5 6 7 8 9
//
// 但当有接收时,仍会运行旧水龙头doNext的内容,所以也会输出 A B C D E F G H I
RACSignal *sequenced = [[letters
    doNext:^(NSString *letter) {
        NSLog(@"%@", letter);
    }]
    then:^{
        return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal;
    }];

Merging

+merge: 前面在flatten中提到的水龙头的合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *merged = [RACSignal merge:@[ letters, numbers ]];
// Outputs: A 1 B C 2
[merged subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];

Combining latest values

+combineLatest: 不论什么时刻取每一个水龙头吐出的最新的那个玻璃球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *combined = [RACSignal
    combineLatest:@[ letters, numbers ]
    reduce:^(NSString *letter, NSString *number) {
        return [letter stringByAppendingString:number];
    }];
// Outputs: B1 B2 C2 C3
[combined subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
[numbers sendNext:@"2"];
[letters sendNext:@"C"];
[numbers sendNext:@"3"];

Switching

-switchToLatest: 取指定的那个水龙头的吐出的最新玻璃球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSubject *signalOfSignals = [RACSubject subject];
RACSignal *switched = [signalOfSignals switchToLatest];
// Outputs: A B 1 D
[switched subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];
[signalOfSignals sendNext:letters];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[signalOfSignals sendNext:numbers];
[letters sendNext:@"C"];
[numbers sendNext:@"1"];
[signalOfSignals sendNext:letters];
[numbers sendNext:@"2"];
[letters sendNext:@"D"];

经常使用宏

RAC 能够看作某个属性的值与一些信号的联动
1
2
3
4

RAC(self.submitButton.enabled) = [RACSignal combineLatest:@[self.usernameField.rac_textSignal, self.passwordField.rac_textSignal] reduce:^id(NSString *userName, NSString *password) {
    return @(userName.length >= 6 && password.length >= 6);
}];

RACObserve 监听属性的改变,使用block的KVO
1
2
3
4

[RACObserve(self.textField, text) subscribeNext:^(NSString *newName) {
     NSLog(@"%@", newName);
}];

UI Event

RAC为系统UI提供了非常多category,非常棒,比方UITextView、UITextField文本框的修改rac_textSignal,UIButton的的按下rac_command等等。

最后

有了RAC,能够不用去担心值什么时候到达什么时候改变,仅仅须要简单的进行数据来了之后的步骤就能够了。

说了这么多,在回过头去看leezhong的比喻和该文最后总结的关系图,再好好梳理一下吧。我也是刚開始学习的人,诚惶诚恐的呈上这篇博文,欢迎讨论,如有不正之处欢迎批评指正。

參考

https://github.com/ReactiveCocoa/ReactiveCocoa

https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/FrameworkOverview.md

https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/BasicOperators.md

http://vimeo.com/65637501
http://iiiyu.com/2013/09/11/learning-ios-notes-twenty-eight/
http://blog.leezhong.com/ios/2013/06/19/frp-reactivecocoa.htmlhttp://nshipster.com/reactivecocoa/

使用ReactiveCocoa实现iOS平台响应式编程相关推荐

  1. IOS响应式编程框架ReactiveCocoa(RAC)使用示例

    本文转载至 http://blog.csdn.net/dfqin/article/details/39164241 IOS ReactiveCocoa RAC 响应式编程 RACSignal Reac ...

  2. [iOS] 响应式编程开发-ReactiveCocoa(一)

    什么是响应式编程 响应式编程是一种面向数据流和变化传播的编程范式.这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播. 例如,在命令式编程环境中 ...

  3. 【iOS架构】iOS ReactiveCocoa函数响应式编程

    声明式编程 声明式编程(declarative programming)是一种编程范型,与命令式编程相对立.它描述目标的性质,让电脑明白目标,而非流程.声明式编程不用告诉电脑问题领域,从而避免随之而来 ...

  4. iOS 高大上函数响应式编程框架ReactiveCocoa学习笔记1 简介

    ReactiveCocoa函数响应式编程 一.简介 ReactiveCocoa(其简称为RAC)是函数响应式编程框架.RAC具有函数式编程和响应式编程的特性.它主要吸取了.Net的 Reactive ...

  5. 响应式编程框架ReactiveCocoa介绍与入门

    ReactiveCocoa是Github团队开发的第三方函数式响应式编程框架,在目前市面上的很多iOS App都大量使用了这个框架.以下我简称这个框架为RAC.我下面会通过几篇博客来和大家一起学习这个 ...

  6. ios架构与开发第五课 BFF、MVVM和响应式编程

    15 跨平台架构:如何设计 BFF 架构系统? 上一模块,我和你介绍了iOS 工程化实践中的基础组件设计, 接下来这部分,我们将进入核心内容:移动端系统架构的设计与实现. 首先请你想一想:如果没有一套 ...

  7. 响应式编程框架ReactiveCocoa学习——框架概览

    这篇博客将会继续翻译RAC的官方文档Framework Overview. 主要是对RAC这和框架进行概览的介绍和学习.同时也可以参考我前面的两篇翻译<响应式编程框架ReactiveCocoa学 ...

  8. 【Unity开源项目精选】UniRx:Unity中的响应式编程

    洪流学堂,让你快人几步.你好,我是你的技术探路者郑洪智,你可以叫我大智. 本篇文章首发于我的公众号:洪流学堂 今天给你分享一个Unity开源项目,我们一起来看看吧! UniRx是什么? UniRx ( ...

  9. UniRx - Unity响应式编程插件

    本文首发于"洪流学堂"公众号. 洪流学堂,让你快人几步!你好,我是你的技术探路者郑洪智,你可以叫我大智(vx: zhz11235). 本文译者:郑洪智 - 你的技术探路者 翻译日期 ...

  10. 【Unity3D插件】UniRx(基于Unity的响应式编程框架)插件学习

    一.介绍UniRx插件 UniRx是一种基于Unity3D的响应式编程框架. UniRx就是Unity版本的Rx响应式扩展,响应式就是观察者和定时器,扩展指的是LINQ的操作符.Rx响应式扩展的特点就 ...

最新文章

  1. 消息幂等(去重)通用解决方案
  2. Debian/linux 安装配置ftp服务器
  3. DVWA学习(三)Brute Force(暴力破解)
  4. Linux ubuntu 修改终端【普通用户、root用户】命令(命令行)配色(颜色)(命令行自动计数)(/etc/profile)(~/bashrc)(source命令)
  5. copy与mutableCopy的内存管理剖析
  6. 获取SQL SERVER某个数据库中所有存储过程的参数
  7. 将字符串和数字合并动态写入
  8. Android 10分钟集成极光推送
  9. 打开浏览器不是主页_对于360浏览器的一些小小改善
  10. SQL查询得到(按编号分组的日期最大的记录)
  11. thinkpad键盘功能键驱动_韩度X-104机械键盘拆解评测 - 性价之选
  12. ArcGIS操作:裁切
  13. Flutter 移动端屏幕适配方案和制作
  14. linux基础总结1
  15. Linux 之shell脚本编程
  16. 轻松办公信息化 六大OA产品优劣点评
  17. android wifi连接优先级,gogo平台靠谱吗-官方网站
  18. 虚拟主机共享IP地址
  19. 计划招收300人,复旦大学计算机学院2022年夏令营来袭
  20. Java 开发环境配置教程

热门文章

  1. 详解回调函数(同步回调,异步回调)
  2. POJ 1221 UNIMODAL PALINDROMIC DECOMPOSITIONS
  3. 搭建SpringMVC
  4. Linux导出/导入逻辑卷组信息
  5. 使用MongoDb连接数据库服务器
  6. mongo(删除操作)
  7. 无法完成验证,可能QQ文件已损坏,您需要重新安装QQ
  8. 升级openssh漏洞
  9. Linux 网卡流量查看
  10. 剑指Offer——重建二叉树