RAC(ReactiveCocoa) 学习--初探

2,656 阅读8分钟

一、简介

1、 函数响应式编程 FRP(Functional Reactive Programming)

听周围的人说,一旦你用熟练掌握了 (RAC)ReactiveCocoa,你就会慢慢依赖上它,懒得再用以前的方法了,因为它用起来实在太爽了。 于是最近我开始学习 RAC 框架,从而了解到了函数响应式编程这一概念。RAC 的核心思想就是函数式 + 响应式编程。 据说 FRP 能让你的代码像数学一样简洁,业务像流水一样清晰流畅。 RAC 属于 Rx 大家族之一,除了 RAC,在 Swift 方面还有 RxSwiftRxSwift 可以看我的另一篇文章《RxSwift 学习(一)—— 初探》

1)响应式编程

例如,在命令式编程环境中,a = b + c 表示将表达式的结果赋给 a,而之后改变 bc 的值不会影响 a。但在响应式编程中,a 的值会随着 bc 的更新而更新。 在响应式编程当中,a = b + c声明的是一种绑定关系。(abc 绑定起来了,所以 bc 的变化会影响 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,项目中引入 <ReactiveObjC.h> 头文件即可开始使用。

下面举几个常用的例子,分别用普通写法和 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<NSKeyValueChangeKey,id> *)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

UITextFieldtextFieldDidBeginEditing 代理为例

普通写法:

需要实现代理方法,增加了代码量,看起来也不方便。

- (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<RACTuple *> *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol; 方法选择器参数**@selector:要实现的具体代理方法 代理名称参数fromProtocol:**对应的代理名称。

下面是在开始编辑self.textField后,控制台打印出来的信息:

2019-05-16 17:02:07.753157+0800 001---RAC
[54156:4473541] <RACTuple: 0x600002b706b0> (
    "<UITextField: 0x7f84fc013e00; frame = (141 128; 97 30); text = ''; opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x60000272b210>; layer = <CALayer: 0x6000029616c0>>"
)

触发代理方法后,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]
 <UIButton: 0x7fb967d118e0; frame = (166 223; 46 256); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x600000942880>>

##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] 
<RACTwoTuple: 0x600000c745e0> (
    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] 
<RACTwoTuple: 0x600000c7c580> (
    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] 
<RACTwoTuple: 0x600000c74660> (
    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<RACSubscriber>  _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)介绍(一)——基本介绍》

转载请备注原文出处,不得用于商业传播——凡几多