ReactiveCocoa简介
ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。
ReactiveCocoa作用
在我们iOS开发过程中,当某些事件响应的时候,需要处理某些业务逻辑,这些事件都用不同的方式来处理。
比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式。
其实这些事件,都可以通过RAC处理
ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。
编程思想
在开发中我们也不能太依赖于某个框架,否则这个框架不更新了,导致项目后期没办法维护,比如之前Facebook提供的Three20框架,在当时也是神器,但是后来不更新了,也就没什么人用了。因此我感觉学习一个框架,还是有必要了解它的编程思想。
先简单介绍下目前咱们已知的编程思想。
3.1 面向过程:处理事情以过程为核心,一步一步的实现。
3.2 面向对象:万物皆对象
3.3 链式编程思想:是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3)
链式编程特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)
代表:masonry框架。
3.4 响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。
代表:KVO运用。
3.5 函数式编程思想:是把操作尽量写成一系列嵌套的函数或者方法调用。
函数式编程特点:每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)
代表:ReactiveCocoa。
ReactiveCocoa结合了几种编程风格:
函数式编程(Functional Programming)
响应式编程(Reactive Programming)
所以,你可能听说过ReactiveCocoa被描述为函数响应式编程(FRP)框架。
以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。
ReactiveCocoa的简单使用
假设我们现在有个登录界面,需要输入username和password,然后点击signinButton,就能发起登录请求
[[[self.usernameTextField.rac_textSignal
map:^id(NSString*text){
return @(text.length);
filter:^BOOL(NSNumber*length){
return [length integerValue] > 3;
subscribeNext:^(id x){
NSLog(@"%@", x);
}];
代码解析:这里给username编辑框的事件添加信号跟踪,将其映射为文本长度,超出长度3的输出日志。而且ReactiveCocoa的方法特性是都会返回类对象自身,方便链式调用。
插入介绍一下名词解释
信号(signal)— RACSignal类
本质:是一种流(流是值的序列化的抽象)
说明:一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。
事件类型:
next:发送数据到下一个管道
error:发送数据失败
completed:发送数据完成
用法:需要订阅不同的事件类型才能发挥作用,即调用下面这些实例方法
- (RACDisposable *)subscribeNext: (void (^)(id x))nextBlock;
- (RACDisposable *)subscribeError: (void (^)(NSError *error))errorBlock;
- (RACDisposable *)subscribeCompleted: (void (^)(void))completedBlock;
例子:ReactiveCocoa框架使用category来为很多基本UIKit控件添加signal。这样你就能给控件添加订阅了,text field的rac_textSignal就是这么来的。
[self.usernameTextField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"x:%@",x);
}];
过滤 — Filter
说明:过滤信号,使用它可以获取满足条件的信号,举个形象的比喻就是一张可以自由设置网口大小的渔网,根据自己需要,对网口进行设置就可以捕到特定规格的鱼。
用法:在用户登录时,我们需要关心用户名长度是否符合要求,比如要求字符长度超过3,那么就可以使用Filter来达到这个目的,如下:
RACSignal *validUsernameSignal =
[self.usernameTextField.rac_textSignal filter:^BOOL(NSString *value) {
return value.length > 3;
}];
映射 — Map
说明:把源信号内容映射成新的内容,简单点说就是将数据改成自己想要的数据。
用法:还是用登录这个场景,我们需要关心用户名长度是否符合要求,比如字符长度超过3才进行下一步处理,如下:
RACSignal *usernameLengthSignal =
[[self.usernameTextField.rac_textSignal map:^id(NSString *value) {
return @(value.length);
}];
状态推导 — RAC()
说明:用于给某个对象的某个属性绑定。
用法:比如只要编辑框文字改变,就会修改label的文字
RAC(self.labelView,text) = _textField.rac_textSignal;
聚合信号
说明:聚合任意数量的信号,然后生成一个新的信号
用法:比如登录按钮只有当用户名和密码输入框的输入都有效时才能进行点击
// 创建聚合信号
RACSignal *signUpActiveSignal =
[RACSignal combineLatest: @[validUsernameSignal, validPasswordSinal]
reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
return @([usernameValid boolValue] && [passwordValid boolValue]);
}];
参照下图,来控制界面对于用户操作的响应。
- (RACSignal *)signInSignal {
return[RACSignal createSignal:^RACDisposable *(id subscriber){
[self.signInService
signInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text
complete:^(BOOL success){
[subscriber sendNext: @(success)];
[subscriber sendCompleted];
}];
returnnil;
}];
}
上面的方法创建了一个信号,使用用户名和密码登录并调用接口验证用户。现在分解来看一下。
上面的代码使用RACSignal的createSignal:方法来创建信号。方法的入参是一个block,这个block描述了这个信号。当这个信号有subscriber时,block里的代码就会执行。
block的入参是一个subscriber实例,它遵循RACSubscriber协议,协议里有一些方法来产生事件,你可以发送任意数量的next事件,或者用error\complete事件来终止。本例中,信号发送了一个next事件来表示登录是否成功,随后是一个complete事件。
这个block的返回值是一个RACDisposable对象,它允许你在一个订阅被取消时执行一些清理工作。当前的信号不需要执行清理操作,所以返回nil就可以了。
可以看到,把一个异步API用信号封装是多简单!
外部订阅信号时,代码可以这样写:
[[[[self.signInButton
doNext:^(id x){
self.signInButton.enabled =NO;
self.signInFailureText.hidden =YES;
flattenMap:^id(id x){
return[self signInSignal];
subscribeNext:^(NSNumber*signedIn){
self.signInButton.enabled =YES;
BOOL success =[signedIn boolValue];
self.signInFailureText.hidden = success;
if(success){
[self performSegueWithIdentifier: @"signInSuccess" sender:self];
}
}];
因为上面方法创建的是内部信号,需要使用flattenMap转换一下。可以看到doNext:是直接跟在按钮点击事件的后面。而且doNext: block并没有返回值。因为它是附加操作,并不改变事件本身。整个流程图如下:
网友评论