一、简介
1、 函数响应式编程 FRP(Functional Reactive Programming)
听周围的人说,一旦你用熟练掌握了 (RAC)ReactiveCocoa,你就会慢慢依赖上它,懒得再用以前的方法了,因为它用起来实在太爽了。
于是最近我开始学习 RAC 框架,从而了解到了函数响应式编程这一概念。RAC 的核心思想就是函数式 + 响应式编程。 据说 FRP 能让你的代码像数学一样简洁,业务像流水一样清晰流畅。
RAC 属于 Rx 大家族之一,除了 RAC,在 Swift 方面还有 RxSwift,RxSwift 可以看我的另一篇文章《RxSwift 学习(一)—— 初探》。
1)响应式编程
例如,在命令式编程环境中,a = b + c 表示将表达式的结果赋给 a,而之后改变 b 或 c 的值不会影响 a。但在响应式编程中,a 的值会随着 b 或 c 的更新而更新。
在响应式编程当中,a = b + c声明的是一种绑定关系。(a 与 b、c 绑定起来了,所以 b、c 的变化会影响 a,这也就是所谓【变化传播】)
2)函数式编程
函数式编程具有以下几个特点:
- 函数是”第一等公民”
所谓”第一等公民”(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。- 闭包和高阶函数
函数式编程抽取了很多常用操作,作为高阶函数,比如map,filter,reduce。 有了这些函数,你的代码将被大大简化,也意味着你可以进行更加快速的开发,同时这些函数也帮助别人理解你的代码。- 不改变状态
不依赖于外部的数据,而且也不改变外部数据的值,而是返回一个新的值给你。- 递归
函数式编程是用递归做为控制流程的机制- 只用“表达式”,不用“语句”,没有副作用
“表达式”(expression)是一个单纯的运算过程,总是有返回值;”语句”(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
原因是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。”语句”属于读写操作,所以就被排斥在外。
函数式编程强调没有”副作用”,意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
2、 subscribeNext 函数
RAC 有一个强大的订阅函数 subscribeNext
- (RACDisposable *)subscribeNext:(void (^)(ValueType _Nullable x))nextBlock;
这个函数可以为任何 RACSignal信号 类型的对象进行订阅,用 block 进行回调。RAC为很多类做了分类,都有返回 RACSignal信号类型,这种方式使得平时很多麻烦的操作都变简洁了,少写了很多代码。
3、RAC的导入
我使用的是 cocoaPods 导入的,使用的是3.0.0版本,在 Podfile 文件里写入pod 'ReactiveObjC', '~> 3.0.0'
,再在命令行中 pod install
,项目中引入
下面举几个常用的例子,分别用普通写法和 RAC 的写法进行对比,来见证一下 RAC 的强大。
二、初级常用用法
1、通知 NSNotificationCenter
普通写法:
1)添加键盘弹出的通知addObserver
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
2)实现通知 keyboardWillShow
- (void) keyboardWillShow:(NSNotification *)note {
NSLog(@"键盘弹出了");
}
3)在析构函数中移除通知removeObserver
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
RAC 写法:
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
NSLog(@"%@",x);
}];
2、KVO
例如,当KVO监听name属性变化
普通写法:
1)添加监听addObserver
[self.textField addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
2)在方法 observeValueForKeyPath
中实现监听到属性值变化后的处理
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([@"text" isEqualToString: keyPath] && object == self.textField) {
NSLog(@"%@", change);
}
}
3)在析构函数中移除监听removeObserver
[self.textField removeObserver:self forKeyPath:@"text"];
RAC 写法:
RAC 中的 KVO 大部分都是宏定义,所以代码异常简洁,简单来说就是RACObserve(TARGET, KEYPATH)
这种形式,TARGET 是监听目标,KEYPATH 是要观察的属性值。
[RACObserve(self.textField, text) subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
self.textField.text = @"凡几多";
3、代理 delegate
以 UITextField 的 textFieldDidBeginEditing
代理为例
普通写法:
需要实现代理方法,增加了代码量,看起来也不方便。
- (void)textFieldDidBeginEditing:(UITextField *)textField {
NSLog(@"开始编辑");
}
RAC 写法:
[[self rac_signalForSelector:@selector(textFieldDidBeginEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"%@",x);
}];
self.textField.delegate = self;
- (RACSignal
*)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol;
方法选择器参数@selector:要实现的具体代理方法
代理名称参数fromProtocol:对应的代理名称。
下面是在开始编辑self.textField后,控制台打印出来的信息:
2019-05-16 17:02:07.753157+0800 001---RAC
[54156:4473541] (
"; layer = >"
)
触发代理方法后,block 回调返回的是元组类型数据。
4、UIButton 按钮点击事件
普通写法:
1)为按钮添加方法 addTarget
[self.button addTarget:self action:@selector(onBtnClick:) forControlEvents:UIControlEventTouchUpInside];
2)实现按钮的点击方法onBtnClick
- (void)onBtnClick:(UIButton *)sender {
NSLog(@"点击按钮了");
}
RAC 写法:
[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"%@",x);
}];
下面是点击按钮后,控制台打印出来的信息:
2019-05-16 17:08:43.841912+0800 001---RAC[54247:4481723]
>
5、UITextField
RAC 写法:
[self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
NSLog(@"%@",x);
}];
当在self.textField输入文字h时,会实时打印出变化后的文字
2019-05-16 18:02:58.562309+0800 001---RAC[54530:4536864] h
2019-05-16 18:02:59.049225+0800 001---RAC[54530:4536864] hh
2019-05-16 18:02:59.288995+0800 001---RAC[54530:4536864] hhh
6、手势 UITapGestureRecognizer
RAC 写法:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
self.label.userInteractionEnabled = YES;
[self.label addGestureRecognizer:tap];
[tap.rac_gestureSignal subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
NSLog(@"%@",x);
}];
7、数组和字典的遍历
普通写法:
普通写法遍历数组和字典都是需要写一个 for 循环进行遍历。
RAC 写法:
1)数组:
NSArray *array = @[@"凡几多",@"感",@"最潇洒"];
[array.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
控制台打印出来的数组信息
2019-05-16 17:14:40.546191+0800 001---RAC[54329:4488306] 凡几多
2019-05-16 17:14:40.546569+0800 001---RAC[54329:4488306] 感
2019-05-16 17:14:40.546761+0800 001---RAC[54329:4488306] 最潇洒
2)字典:
NSDictionary *dict = @{@"name":@"凡几多",@"age":@"20",@"sex":@"男"};
[dict.rac_sequence.signal subscribeNext:^(id _Nullable x) {
//元祖
NSLog(@"%@",x);
RACTwoTuple *tuple = (RACTwoTuple *)x;
NSLog(@"key == %@ , value = %@",tuple[0],tuple[1]);
}];
控制台打印出来的数组信息
2019-05-16 17:20:02.538642+0800 001---RAC[54365:4494005]
(
name,
"\U51e1\U51e0\U591a"
)
2019-05-16 17:20:02.539301+0800 001---RAC[54365:4494005]
key == name , value = 凡几多
2019-05-16 17:20:02.540359+0800 001---RAC[54365:4494005]
(
age,
20
)
2019-05-16 17:20:02.540577+0800 001---RAC[54365:4494005]
key == age , value = 20
2019-05-16 17:20:02.542622+0800 001---RAC[54365:4494005]
(
sex,
"\U7537"
)
2019-05-16 17:20:02.542774+0800 001---RAC[54365:4494005]
key == sex , value = 男
三、RAC最基本的用法流程
创建信号、订阅信号、发送信号
//1:创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
//subscriber 对象不是一个对象
//3:发送信号
[subscriber sendNext:@"Cooci"];
//请求网络 失败 error
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:10086 userInfo:@{@"key":@"10086错误"}];
[subscriber sendError:error];
// RACDisposable 销毁
return [RACDisposable disposableWithBlock:^{
NSLog(@"销毁了");
}];
}];
//2:订阅信号
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
//订阅错误信号
[signal subscribeError:^(NSError * _Nullable error) {
NSLog(@"%@",error);
}];
以上的总结参考了并部分摘抄了以下文章,非常感谢以下作者的分享!:
1、作者zzfx的《函数响应式编程(FRP)从入门到”放弃”——基础概念篇》
2、作者Philm_iOS的《函数响应式编程》
3、作者我只不过是出来写写代码的《RAC(ReactiveCocoa)介绍(一)——基本介绍》
转载请备注原文出处,不得用于商业传播——凡几多