首先声明:接下来我研究的RAC是基于2.5版本的。虽然RAC是作为一个OC的框架开始的,但是从 版本3.0开始,所有的主要特性开发都是基于 Swift API。RAC的Objective-C API和Swift API是完全分开的,但是有一个bridge可以将他们互相转换。这主要是为了兼容使用RAC的一些老项目,或者用于一些还没有被加入Swfit API的一些Cocoa扩展。Objective-C API将继续存在,在可预计的未来将提供支持,但是不会有新的改进。关于使用这些API的信息可以参考:legacy documentation。
想要学习更新的版本请点击这里
在学习RAC之前我们需要了解几个概念:
RAC核心思想:就是
事件
发生后作出响应
核心概念:信号
Signal
订阅者Subscriber
-
所做事情:就是用 信号 接管了 iOS 中的所有事件
- iOS开发中的事件
- target
- delegate
- KVO
- 通知
- 网络异步回调
- 时钟
- iOS开发中的事件
-
框架特点
- 超重量级的核心框架,学习成本较高
利用 信号,接管 iOS 的所有事件
利用 Block 将所有相关代码集中在一起,从一定程度上解决了代码分散的问题
使用时需要注意循环引用,注册 rac_willDeallocSignal 信号能够跟踪对象是否被释放
通过 KVO 监听,能够及时将模型数据变化体现在界面上
RAC实现过程就是向订阅者发送信号
RAC 核心结构图
RAC简单了解
接下来我们就在代码中来学习一下实现过程
首先是创建一个新的工程,然后我们导入ReactiveCocoa
框架
,我们可以在这里直接下载,拖进去,也可以使用cocoapods
来安装
platform:ios, '8.0'
pod 'ReactiveCocoa', '~> 2.5'
如图:
PersonModel里面有两个属性name
,age
,负责创建模型。PersonViewModel里面负责发送信号。
我们首先在PersonViewModel里面发送一个信号,具体写法如下
我们需要在PersonViewModel里面发送一个信号,需要注意的是里面block有一个id
返回值
信号分为热信号和冷信号,只有有订阅者了,才是热信号,我们在controller里面订阅一下.订阅需要用subscribeNext
方法。我们点击可以发现一个联想出了4中方法,我们选择最长的讲解一下
最长这个方法有3个block方法
- subscribeNext
- error 通知订阅者出现错误
- completed 通知订阅者监听完成
我们能现在写的代码有,在PersonViewModel
里面发送信号,在viewController
里面接受信号。
代码如下
发送信号
- (RACSignal *)loadPersons {
// 直接返回一个 RAC 的信号
// 一旦有了订阅者,block 内部的代码就能够执行
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送信号");
return nil;
}];
}
订阅者接受信号
PersonViewModel *viewModel = [[PersonViewModel alloc]init];
[[viewModel loadPersons]subscribeNext:^(id x) {
NSLog(@"x==%@",x);
} error:^(NSError *error) {
NSLog(@"Error===%@",error);
} completed:^{
NSLog(@"信号完成");
}];
打印结果
结果只是执行了发送信号block里面的内容
我们可以看到,订阅者有3种block返回情况,所以我猜测,在发送信号的时候也会有多种情况,我们点击```RACSubscriber`进入看一看。
发现里面的确有三种方法
- (void)sendNext:(id)value;
- (void)sendError:(NSError *)error;
- (void)sendCompleted;
我们把三种方法写全,并把返回值给添加上看看能够打印什么结果
当我们设置BOOL isError = YES;
时打印结果
通过以上比较,我们大概知道了,信号时怎么使用了吧。基础的流程就这些。
RAC信号实用
前面我们说过RAC是超重级框架,利用 信号,接管 iOS 的所有事件。我们来看一下RAC的基本框架构成
其中core 文件
中,就是RAC的核心代码,像信号的发送等,UI文件
中,跟系统的功能一样,也有button,label等,都是继承自controller
简单学习1
我们创建一个textfield控件,并监听输入内容。我们要监听内容变化,我们先进入RAC UI中查找textfield都有什么方法。
打开可以发现一个有两个方法,其中第一个方法就是,发送信号的方法
- (RACSignal *)rac_textSignal;
- (RACChannelTerminal *)rac_newTextChannel;
我们代码实现过程
UITextField *nameTF = [[UITextField alloc]initWithFrame:CGRectMake(50, 100, 200, 40)];
nameTF.backgroundColor = [UIColor grayColor];
[self.view addSubview:nameTF];
// 监听文本输入内容 - 参数就是输入的文本内容!
[[nameTF rac_textSignal]subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
简单学习2:组合信号(combineLatest)
譬如:当我们有两个textfield需要监听textfield变化时,难道需要写两遍[[nameTF rac_textSignal]subscribeNext
这个方法吗,那岂不是很麻烦。RAC为了解决这个麻烦提出了组合信号(combineLatest)这个概念。
代码实现
UITextField *nameTextField = [[UITextField alloc] initWithFrame:CGRectMake(20, 40, 300, 40)];
nameTextField.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:nameTextField];
UITextField *pwdTextField = [[UITextField alloc] initWithFrame:CGRectMake(20, 80, 300, 40)];
pwdTextField.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:pwdTextField];
// 组合信号 Tuple - 元组,可以包含多值,通过 1,2,3,4,5,last 获取
[[RACSignal combineLatest:@[nameTextField.rac_textSignal, pwdTextField.rac_textSignal]] subscribeNext:^(RACTuple *x) {
NSString *name = x.first;
NSString *pwd = x.second;
NSLog(@"%@ %@", name, pwd);
}];
注意点
RAC 在使用的时候,因为系统提供的
信号
是始终存在的!
因此,所有的 block 中,如果出现
self.
/成员变量
几乎百分百会循环引用!
解除循环的方法
- __weak
- 利用 RAC 提供的 weak-strong dance
在 block 的外部使用 @weakify(self)
在 block 的内部使用 @strongify(self)
RAC 响应式编程
RAC被广大程序员所极力推崇的重要原因就是双向绑定,响应式
编程!MVVM能被运用也离不开RAC的响应式编程。
举个例子
b = 3;
c = 4;
a = b + c; 7
b = 100; a 的值不会发生改变
响应式
,当修改 b / c 的时候 a 同时发生变化!
在 iOS 开发中,可以使用 KVO 监听对象的属性值,达到这一效果!
因为 苹果的 KVO 会统一调用同一个方法,方法是固定的,如果监听属性过多,方法非常难以维护!
RAC 是目前实现响应式编程的唯一解决方案!
代码实现
_person.name = @"zhangsan";
_person.age = 18;
UITextField *nameTextField = [[UITextField alloc] initWithFrame:CGRectMake(20, 80, 300, 40)];
nameTextField.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:nameTextField];
UITextField *ageTextField = [[UITextField alloc] initWithFrame:CGRectMake(20, 140, 300, 40)];
ageTextField.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:ageTextField];
// 双向绑定
// 1> 模型(KVO 数据) => UI(text 属性)
// a) name(string) -> text(string)
RAC(nameTextField, text) = RACObserve(_person, name);
NSLog(@"%@", RACObserve(_person, name));
// b) age(NSInteger) -> text(string) RAC 中传递的数据都是 id 类型
/**
- 如果使用 基本数据类型绑定 UI 的内容,需要使用 map 函数,通过 block 对 value 的数值进行转换之后
- 才能够绑定
*/
RAC(ageTextField, text) = [RACObserve(_person, age) map:^id(id value) {
NSLog(@"%@ %@", value, [value class]);
// 错误的转换,value 本身已经是 NSNumber,需要字符串
// return [NSString stringWithFormat:@"%zd", value];
return [value description];
}];
// 2> UI => 模型的绑定
[[RACSignal combineLatest:@[nameTextField.rac_textSignal, ageTextField.rac_textSignal]] subscribeNext:^(RACTuple *x) {
_person.name = [x first];
_person.age = [[x second] integerValue];
}];
// 3> 添加按钮,输出结果
UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
btn.center = self.view.center;
[self.view addSubview:btn];
[[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
// !!!循环引用
NSLog(@"%@ %zd", _person.name, _person.age);
}];
_person
Person类里面有两个属性,name和age
,name字符串,age int类型