使用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. [转]使用ReactiveCocoa实现iOS平台响应式编程

    原文:http://www.itiger.me/?p=38 使用ReactiveCocoa实现iOS平台响应式编程 ReactiveCocoa和响应式编程 在说ReactiveCocoa之前,先要介绍 ...

  2. ReactiveCocoa,最受欢迎的iOS函数响应式编程库(2.5版),没有之一!

    简介 项目主页: ReactiveCocoa 实例下载: https://github.com/ios122/ios122 简评: 最受欢迎,最有价值的iOS响应式编程库,没有之一!iOS MVVM模 ...

  3. iOS响应式编程:ReactiveCocoa vs RxSwift 选谁好

    转载: iOS响应式编程:ReactiveCocoa vs RxSwift 选谁好 内容来自stack overflow的一个回答:ReactiveCocoa vs RxSwift – pros an ...

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

    ReactiveCocoa是响应式编程(FRP)在iOS中的一个实现框架,它的开源地址为:https://github.com/ReactiveCocoa/ReactiveCocoa# :在网上看了几 ...

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

    ReactiveCocoa是响应式编程(FRP)在IOS中的一个实现框架,它的开源地址为:https://github.com/ReactiveCocoa/ReactiveCocoa# :在网上看了几 ...

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

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

  7. iOS开发之OC篇-响应式编程Reactive Cocoa

    一.Reactive Cocoa 介绍 Reactive Cocoa 是 iOS 开发的一个 "重量级" 框架 高大上的概念:响应式编程 核心概念:信号 Signal 官方网站:h ...

  8. 函数响应式编程(FRP)框架--ReactiveCocoa

    由于工作原因,有段时间没更新博客了,甚是抱歉,只是,从今天開始我又活跃起来了,哈哈,于是决定每周更新一博.大家互相学习.交流. 今天呢.讨论一下关于ReactiveCocoa,这个採用函数响应式编程( ...

  9. 深入浅出-iOS函数式编程的实现 && 响应式编程概念

    简介 本篇主要回顾一下--iOS函数式编程 && 响应式编程概念 ,如何一步步实现函数式编程的过程,对阅读Masonry && SnapKit源码有一定的帮助. 配图 ...

随机推荐

  1. mysql 创建函数

    <pre name="code" class="html">root 用户创建函数: delimiter $$ CREATE FUNCTION `l ...

  2. C++晋升之std中vector的实现原理(标准模板动态库中矢量的实现原理)

    我们实现的数据结构是为了解决在执行过程中动态的开辟空间使用(比如我们不停的输入,输入的多少我们不确定) 假设当你看到这篇文章的话,就当作是零食咀嚼,营养没有有BUG,能够直接看我博客中文章:CPU对内 ...

  3. 14.5.7 Storing InnoDB Undo Logs in Separate Tablespaces 存储InnoDB Undo logs 到单独的表空间

    14.5.7 Storing InnoDB Undo Logs in Separate Tablespaces 存储InnoDB Undo logs 到单独的表空间 在MySQL 5.6.3,你可以存 ...

  4. SDL Guide 中文译版

    SDL即Simple DirectMedia Layer,类似DirectX,是完整的游戏.多媒体开发包,但不同的是它跨越几乎所有的平台,有各种语言的接口,多种语言的文档,而这一切都是广大志愿者完成的 ...

  5. 第二章 IoC Setter注入

    Setter注入又称为属性注入.是通过属性的setXXX()方法来注入Bean的属性值或依赖对象.由于Setter注入具有可选择性和灵活性高的优点,因此Setter注入是实际应用中最常用的注入方式. ...

  6. android在Canvas使用drawBitmap画一幅画

    1.画图的主要方法 //Bitmap:图片对象,left:向左偏移.top: 顶部偏移     drawBitmap(Bitmap bitmap, float left, float top, Pai ...

  7. 关于Oralce数据库优化的几点总结

    个人理解,数据库性能最关键的因素在于IO,因为操作内存是快速的,但是读写磁盘是速度很慢的,优化数据库最关键的问题在于减少磁盘的IO,就个人理解应该分为物理的和逻辑的优化, 物理的是指oracle产品本 ...

  8. Program1-1

    package com.algo; import java.util.Random; import java.util.Scanner; public class P1_1 { static int ...

  9. STM32M CUBE实现printf打印调试信息以及实现单字节接收

    在写单片机程序时我们一般喜欢使用printf来通过串口打印调试信息,但这个函数是不能够直接使用的.必须做点对库函数的修改. 具体project下载地址: http://download.csdn.ne ...

  10. GIT的下载、安装、与使用

    一.下载: 网址:https://code.google.com/p/msysgit/ 进入这个网站以后,你会看到以下界面: 在这个界面中找到: 这时你便可以下载GIT 二.安装 安装过程比较简单,一 ...