1.ReactiveCocoa简介
RAC 是一个 iOS 中的函数式响应式编程框架,是Github 在开发 GitHub for Mac 过程中的一个副产品,他提供了一系列用来组合和转换值流的 API,为事件的处理定义了一个标准接口,大大方便了开发者去管理各种事件的处理,结果就是开发者能从关注业务实现的细节上脱身,转变为开发者只需要关心业务本身就好了,是不是听起来很神奇的样子,接下来就让我们进入开发的另一片天空吧!
2.ReactiveCocoa作用
RAC关键解决的问题是开发中经常回见的“低聚合,高耦合”问题。
在RAC出现之前,我们编写iOS代码,大部分都是在响应一些事件:按钮点击、接收网络消息、属性变化等等。但处理事件的形式在苹果官方API中却有好几种:如target-action、代理方法、KVO、回调或其它。以上这几种,往往在一个项目中基本都会使用到,在不同的地方会出现很多处理事件的形式,这就带来了不能很好统一管理问题。因此,我们想,有没有一个统一管理的解决方案呢?这个方案又是怎样的呢?到这里ReactiveCocoa就该粉墨登场了,它出现的目的就是为了解决统一标准去管理代码中的事件。
3.编程思想
ReactiveCocoa结合了几种编程风格:
函数式编程(Functional Programming):使用高阶函数,例如函数用其他函数作为参数。
响应式编程(Reactive Programming):关注于数据流和变化传播。
所以,你可能听说过ReactiveCocoa被描述为函数响应式编程(FRP)框架。
以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。
4.底层原理
1、运用的是Hook(钩子)思想,Hook是一种用于改变API(应用程序编程接口:方法)执行结果的技术.
2、Hook用处:截获API调用的技术。
3、Hook原理:在每次调用一个API返回结果之前,先执行你自己的方法,改变结果的输出。
5.常见类和常见功能
信号(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]);
}];
// 绑定按钮
[signUpActiveSignal subscribeNext:^(NSNumber *signupActive) {
@strongify(self)
self.signInButton.enabled = [signupActive boolValue];
}];
创建信号
说明:创建一个信号,用于执行信号中的Block,只要有订阅者,Block中的内容就会被执行。
用法:比如点击按钮进行登录时,就可以创建一个信号,然后进行订阅,然后执行登录的业务逻辑处理
// 创建信号
- (RACSignal *)signInSignal {
@weakify(self)
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
@strongify(self)
[self.signInService signInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text
complete:^(BOOL success) {
[subscriber sendNext:@(success)];
// 必须创建完成时间
[subscriber sendCompleted];
}];
return nil;
}];
}
// 点击登录按钮
[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside]
map:^id(id value) {
@strongify(self)
return [self signInSignal];
}] subscribeNext:^(id x) {
NSLog(@"Sign in result: %@", x);
}];
信号中的信号 — map —> flattenMap
说明:换句话说就是一个外部信号里面还有一个内部信号。
用法:比如上面这个栗子,订阅后发送的数据 x 类型最终还是信号,而不是创建信号时发送的NSNumber类型:
// 点击登录按钮(Map)
[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside]
map:^id(id value) {
@strongify(self)
return [self signInSignal];
}] subscribeNext:^(id x) {
NSLog(@"Sign in result: %@", x);// X类型还是RACSignal
}];
// 点击登录按钮(FlattenMap)
[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside]
flattenMap:^id(id value) {
@strongify(self)
return [self signInSignal];
}] subscribeNext:^(id x) {
NSLog(@"Sign in result: %@", x);// X类型是NSNumber
}];
FlatternMap和Map的区别:
FlatternMap中的Block返回信号。
Map中的Block返回对象。
开发中,如果信号发出的值不是信号,映射一般使用Map
开发中,如果信号发出的值是信号,映射一般使用FlatternMap
总结:signalOfsignals用FlatternMap。
添加附加操作(doNext:)
说明:执行Next之前,会先执行这个Block,简单说就是在一段逻辑执行前进行拦截,然后先执行一段特别操作,再操作接下来的逻辑
- (RACSignal *)doNext:(void (^)(id x))block
用法:还是拿登录的场景来说,当登录service正在校验用户名和密码时,登录按钮应该是不可点击的。这会防止用户多次执行登录操作。还有,如果登录失败了,用户再次尝试登录时,应该隐藏错误信息。
6.总结
ReactiveCocoa的核心就是信号,而它不过就是事件流,通过订阅将数据发送出去。以上就是本篇笔记的核心概念,哈哈,是不是很简单呢?
通过以上一些概念和使用场景,我们大概对RAC有了初步的认识。基础有了,接下来学习深入的知识点就容易多了。
参考
1、RayWenderlich ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2
2、iOS的函数响应型编程
3、MISSAJJ琴瑟静听( Swift 和 Objective-C )iOS 开发项目电子书