ReactiveCocoa是iOS开发的一个开源第三方框架,又被称作函数响应是编程框架。它结合了函数式编程和响应式编程两种编程风格,定义了一个标准的接口来响应诸如点击按钮,网络请求完成等事件。
函数式编程&响应式编程
- 何谓函数式编程
函数式编程就是把操作任务尽量写成一系列嵌套的函数或方法来调用。
这种编程思想有几个特点:方法的返回值是他本身,参数是Block,如果对block不太熟悉的同学,可以先看一下这篇文章:iOS Block,做一下了解。
- 何谓响应式编程
我们可以用KVO来理解响应式编程。一个类中有一个user对象,user有“userId”“userName”两个属性,我们利用KVO来观察user的userName属性,当userName属性变化了的时候,会执行下面的方法。
#import "HomeViewController.h"
#import "User.h"
@interface HomeViewController ()
@property (nonatomic, strong) User *user;
@end
@implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.user = [User new];
self.user.userName = @"张三";
self.user.userId = 10086;
[self.user addObserver:self forKeyPath:@"userName" options:
|NSKeyValueObservingOptionNew context:nil];
NSLog(@"%@", self.user);
self.user.userName = @"李四";
self.user.userId = 10010;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"%@%ld", self.user.userName, self.user.userId);
}
添加ReactiveCocoa到工程
和其他的第三方一样,添加ReactiveCocoa的方式有两种:
- 通过CocoaPods添加
#RAC
pod 'ReactiveCocoa', '~> 2.5'
- 通过GitHub手动下载ReactiveCocoa,根据文档添加到工程。
ReactiveCocoa常用的类
1. RACSignal(信号)
RACSignal是RAC中最核心的类,如果把它学懂了, 就已经可以用ReactiveCocoa进行开发了。
可以将RACSingnal当做一个传递信号的工具,当数据变化的时候,会将改变的信息发送出去,订阅者接受到这个信息,执行相应的方法。
信号我们将它分为两种:一种是冷信号,也是默认的信号,这种信号,即使数据改变了,也不会发送信息;当有订阅者订阅了这个信号,它才会变成热信号,当数据变化,它会将改变的数据发送出去,具体的,我们来看看RACSignal是怎么使用的:
// 1 创建一个信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 3 发送数据
[subscriber sendNext:@"第一个信号"];
return [RACDisposable disposableWithBlock:^{
NSLog(@"取消信号");
}];
}];
/*********** 到这里还是“冷信号” ***********/
// 2 订阅信号(冷信号变热信号)
[signal subscribeNext:^(id x) {
// 4 处理信号
NSLog(@"%@", x);
}];
这里面写了RACSignal基本的操作,信号的创建,订阅以及发送。
2. RACSubscriber
上面的代码中我们还看到了两个类,在创建信号时block返回的类型 RACDisposable和参数 RACSubscriber。
RACSubscriber:表示订阅者的意思,在通过creat创建信号的时候,都会有一个订阅者,来发送数据。
3. RACDisposable
RACDisposable:是用来取消订阅、清理资源的。
这里我们还有一种写法,来手动取消订阅,这种情况,我们需要先来实例化个对象再来调用dispose方法:
// 订阅信号
RACDisposable *disposable = [signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 取消订阅
[disposable dispose];
4. RACSubject & RACReplaySubject
RACSubject 是信号提供者,它既可以充当信号,又可以发送信号
// 1创建信号
RACSubject *subject = [RACSubject subject];
// 2订阅信号
[subject subscribeNext:^(id x) {
// 4处理信号
NSLog(@"订阅:%@", x);
}];
// 3发送信号
[subject sendNext:@"信号"];
RACReplaySubject:是RACSubject的子类,重复提供信号。
// 创建信号
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 发送信号
[replaySubject sendNext:@"信号1"];
// 订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"订阅1:%@", x);
}];
// 发送信号
[replaySubject sendNext:@"信号2"];
// 订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"订阅2:%@", x);
}];
RACReplaySubject和RACSubject的最大的区别在于:
RACReplaySubject既可以先发送信号再订阅,也可以先订阅信号,再发送;而RACSubject只能先订阅信号,再发送。
5.RACTuple(元组)
在Objective-C中,本身是没有元组的,在Swift中有元组的概念。它与数组类似,可以以数组来初始化:
NSArray *arr = @[@"张三", @"10086", @"boy"];
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:arr];
NSLog(@"\nname:%@\nid:%@\ngender:%@", [tuple objectAtIndex:0], [tuple objectAtIndex:1], [tuple objectAtIndex:2]);
ReactiveCocoa常用的方法
1. 监听事件
// 1. 监听事件
[[self.bt_Go rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"被点击");
HomeViewController_2 *vc_2 = [[HomeViewController_2 alloc] initWithNibName:@"HomeViewController_2" bundle:nil];
vc_2.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:vc_2 animated:YES];
}];
2. 监听通知
// 2. 监听通知
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"socketMessage" object:nil] subscribeNext:^(id x) {
NSLog(@"收到!");
}];
3. 监听TextField的变化
// 3. 监听TextField变化
[self.tx_userName.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
4. 监听变量属性(KVO)
// 4. 监听变量属性
self.user = [User new];
self.user.userId = 10086;
self.user.userName = @"张三";
[[self.user rac_valuesForKeyPath:@"userName" observer:nil] subscribeNext:^(id x) {
NSLog(@"用户的名字改变了:%@", x);
}];
self.user.userName = @"李四";
5. 充当代理
// 5. 充当代理
self.headerView = [[[NSBundle mainBundle] loadNibNamed:@"HomeHeaderView" owner:self options:nil] lastObject];
self.headerView.frame = CGRectMake(0, SCREEN_HEIGHT - 300, SCREEN_WIDTH, 300);
[self.view addSubview:self.headerView];
[self.headerView.subject subscribeNext:^(User *user) {
NSLog(@"收到信号:\n用户名:%@,用户id:%ld", user.userName, user.userId);
}];
ReactiveCocoa常用的宏
1. RAC(TARGET, ...)
1.1 用途:
用来将某个对象的某个属性和某个信号绑定。当有信号过来,会自动赋值给对象的属性。
1.2 用法:
// lb_name 是一个UILable, tx_userName是UITextField
RAC(self.lb_name, text) = self.tx_userName.rac_textSignal;
1.3 对比:
我们实现以下,在一个textfield中输入内容时,将它实时输入到一个label中,看看以下几种方法是怎么实现的,哪个更简单。
- 不用RAC,通过给TextField添加方法来实现
- (void)viewDidLoad {
[super viewDidLoad];
[self.tx_userName addTarget:self action:@selector(showtextFiledContents) forControlEvents:UIControlEventEditingChanged];
}
- (void) showtextFiledContents{
self.lb_label1.text = self.tx_userName.text;
}
- 利用RAC绑定属性和信号
[self.tx_userName.rac_textSignal subscribeNext:^(NSString *x) {
self.lb_label1.text = x;
}];
- 用RAC(TARGET, ...)实现
RAC(self.lb_label1, text) = self.tx_userName.rac_textSignal;
2. RACObserve(TARGET, KEYPATH)
2.1 用途:
监听某个对象的某个属性,实际上它是[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]
这句代码的宏
2.2 用法
[RACObserve(self.user, userName) subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
2.3 对比
实现监听user的userName属性
- 不使用 RACObserve,直接监听
self.user = [User new];
self.user.userId = 10086;
self.user.userName = @"张三";
[[self.user rac_valuesForKeyPath:@"userName" observer:nil] subscribeNext:^(id x) {
NSLog(@"用户的名字改变了:%@", x);
}];
[[self.bt_Go rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
self.user.userName = @"李四";
}];
- 使用 RACObserve
self.user = [User new];
self.user.userId = 10086;
self.user.userName = @"张三";
[RACObserve(self.user, userName) subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[[self.bt_Go rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
self.user.userName = @"李四";
}];
3. @weakify(Obj)和@strongify(Obj)
3.1用途:
防止循环引用
3.2 用法:
@weakify(Obj)和@strongify(Obj)是配合使用的,@weakify(Obj)写在block外,@strongify(Obj)写在block内
@weakify(self)
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
@strongify(self)
[subscriber sendNext:self];
return nil;
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
4. RACTuplepack
用途:
把数据包装成RACTuple(元组类)
用法:
RACTuple *tuple = RACTuplePack(@"123",@1);
5. RACTupleUnpack
用途:
把RACTuple(元组类)解包成对应的数据
用法:
RACTupleUnpack(NSString *str,NSNumber *num) = tuple;
NSLog(@"%@ %@",str,num);