使用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
&
gt
;
0
&
amp
;
&
amp
;
email
.
length
&
gt
;
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
&
lt
;
RACSubscriber
&
gt
;
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
&
lt
;
RACSubscriber
&
gt
;
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
&
gt
;
=
6
&
amp
;
&
amp
;
password
.
length
&
gt
;
=
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.html
http://nshipster.com/reactivecocoa/
更多文章、技术交流、商务合作、联系博主
微信扫码或搜索:z360901061
微信扫一扫加我为好友
QQ号联系: 360901061
您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。
【本文对您有帮助就好】元