RAC1-介绍
简介
GitHub团队开发的一套开源框架;超重量级的框架;接管了苹果中的所有消息机制;
全称ReactiveCocoa,响应式编程(FRP)在iOS中的一个实现框架,现在OC版本的只到3.0.0版本就不再更新了,swift版本有持续更新;
目的:事件监听
用信号接管了苹果中的所有事件机制,包括:
- addTarget:监听,点击按钮之后调用回调方法;
- 代理(delegate):在主控件里发生某个事情的时候,通过协议的方式通知我们去执行协议的方法;双方有个约定好的协议,调用方实现协议方法,接收方在需要的时候通知调用方delegate,执行协议方法。
- 通知:通过注册字符串的方式,当接收到通知之后,监听者去做一些事情;
- KVO:通过监听属性变化来实现监听
- 时钟
- 网络异步回调
block不是事件机制,block是提前好的代码传递给接受方,接收方在拿到block之后,在需要的时候直接执行block,不再跟回来有关系了。从调用方来说,准备好的block,传给接收方,我们是不管接收方什么时候执行的。所以,block不属于事件监听。
特点
学习起来非常难;团队开发时需特别谨慎,需要不断地代码评审,保证团队中所有人的代码风格一致!
RAC的重要概念
信号
RAC的核心思想
所谓响应,就是事件发生后做出响应
框架特点
- 超重量级的核心框架,学习成本较高
- 利用信号,接管iOS的所有事件
- 利用block将所有相关代码集中在一起,从一定程度上解决了代码分散的问题;
- 使用时需要注意循环引用,注册 rac_willDeallocSignal 信号能够跟踪对象是否被释放
- 通过KVO监听,能够及时将模型数据变化体现在界面上
RAC最为核心的概念:1>RAC接管了了所有的事件机制,2>信号刚刚创建的时候,是冷信号,不会工作,只有被订阅者订阅了之后才是热信号,才会执行。3>如何传递信息:在信号内部给订阅者通过send三个方法告诉订阅者他对应的方法,订阅者只需要监听不同的代码块,就可以在不同的代码块获得自己想要的东西了。
网络请求的时候,使用RAC实现监听,订阅者收到相应的方法时,获取自己想要的东西,并作出相应的操作。
XZPersonListModel.h
//列表数据模型,负责加载数据(包含网络数据/本地缓存数据)
@interface XZPersonListModel :NSObject
//联系人数组,泛型数组
@property (nonatomic)NSMutableArray
<XZPerson *> *personList;
//加载联系人数组返回一个RAC的信号
- (RACSignal
*)loadPersons;
@end
XZPersonListModel.m
- (RACSignal
*)loadPersons {
NSLog(@"==============%s",__FUNCTION__);
//直接返回一个
RAC的信号
//一旦有了订阅者,block内部的代码能够执行
return
[RACSignalcreateSignal:^RACDisposable
* _Nullable(id<RACSubscriber> _Nonnull
subscriber) {
//发送不同的信号
_personList
= [NSMutableArrayarray];
//模拟异步加载数据
dispatch_async(dispatch_get_global_queue(0,0),
^{
//模拟延时
[NSThreadsleepForTimeInterval:1.0];
//创建数据
for
(NSInteger i =0;
i <20; i++) {
XZPerson
*person = [[XZPersonalloc]init];
person.name
= [@"zhangsan - "stringByAppendingFormat:@"%ld",(long)i];
person.age
= 15 +arc4random_uniform(20);
[_personListaddObject:person];
}
NSLog(@"%@",_personList);
//完成回调发送信号给订阅者,主线程
dispatch_async(dispatch_get_main_queue(),
^{
BOOL
isError =NO;
if
(isError) {
[subscribersendError:[NSErrorerrorWithDomain:@"cn.xzproject.error"code:1001userInfo:@{@"error
message":@"异常错误"}]];
}else
{
[subscribersendNext:_personList];
}
//发送完成事件
[subscribersendCompleted];
});
});
returnnil;
}];
}
ViewController.m
- (void)loadData
{
// 1.实例化视图模型
_personListModel
= [[XZPersonListModelalloc]init];
// 2.加载数据
/**
next是接收到数据
error接收到错误,错误处理
completed信号完成
*/
[[_personListModelloadPersons]subscribeNext:^(id _Nullable
x) {
NSLog(@"==============%@",x);
//刷新数据
[self.tableNoticereloadData];
}error:^(NSError
* _Nullable error) {
NSLog(@"==============%@",error);
}completed:^{
NSLog(@"==============完成");
}];
}
RAC-监听按钮和输入框事件
可以查看框架中的各种方法,RAC封装了常用的UI控件方法,需要监听哪个控件,查看一下这个控件或者他的父类的方法即可。
因为UIButton是继承自UIControl的,UIButton分类中没有合适的方法,所以,可以使用UIControl分类的方法
监听按钮的事件 -不再需要新建一个方法,在block里面实现相应事件,用block把重构的相关代码放在一起
[[btnrac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(__kindofUIControl
*_Nullable x) {
NSLog(@"%@---------%@",x,[xclass]);
}];
// [btn rac_signalForControlEvents:UIControlEventTouchUpInside]是创建了一个冷信号,调用subscribeNext才订阅了信号,才会工作
监听文本输入框内容 -参数就是输入的文本内容!
[[nameTextFieldrac_textSignal]subscribeNext:^(NSString
* _Nullable x) {
NSLog(@"%@
%@",x,[xclass]);
}];
组合信号
创建两个UITextField,将这两个的输入框的信号进行组合
UITextField *nameTextField = [[UITextFieldalloc]initWithFrame:CGRectMake(20,40,300,40)];
nameTextField.borderStyle
= UITextBorderStyleRoundedRect;
[self.viewaddSubview:nameTextField];
UITextField
*pwdTextField = [[UITextFieldalloc]initWithFrame:CGRectMake(20,100,300,40)];
pwdTextField.borderStyle
= UITextBorderStyleRoundedRect;
[self.viewaddSubview:pwdTextField];
//组合信号Tuple是元组,
[[RACSignalcombineLatest:@[nameTextField.rac_textSignal,pwdTextField.rac_textSignal]]subscribeNext:^(RACTuple
* _Nullable x) {
NSString
*name = x.first;
NSString
*pwd = x.second;
NSLog(@"name:%@
pwd:%@ [x class]:%@",name,pwd,[xclass]);
//打印结果===name:Wertyui
pwd:3456yui [x class]:RACTuple
}];
// reduce ->减少的意思,合并两个信号的数据,进行汇总计算时使用的!
// id是返回值,参数是有括号的;
// reduce中,可以通过接收的参数进行计算,并且返回需要的数值!例:登录界面,只有用户名和密码同时存在,才允许登录!
//方法一:使用__weak避免循环引用
// __weak typeof(self)weakSelf = self;
//方法二:
@weakify(self);
[[RACSignalcombineLatest:@[nameTextField.rac_textSignal,pwdTextField.rac_textSignal]reduce:^id(NSString
*name, NSString *pwd){
NSLog(@"%@
%@",name,pwd);
//判断用户名和密码是否同时存在,需要转换成NSNumer类型,才能被当做
id 传递
return@(name.length
> 0 && pwd.length
> 0);
}]subscribeNext:^(id _Nullable
x) {
NSLog(@"%@",x);
@strongify(self);
self.btn.enabled
= [x boolValue];
// weakSelf.btn.enabled = [x boolValue];
}];
}
// RAC在使用的时候,因为系统提供的信号是始终存在的!因此,所有的block中,如果出现'self.'
/ '成员变量'几乎百分之百会循环引用!
/**
解除循环引用的方法
1.__weak
2.利用
RAC 提供的 weak-strong dance
在
block的外部使用 @weakify(self)
在
block的内部使用 @strongify(self)
然后,直接使用self即可。
*/
//成员变量不好用weak
RAC4-使用RAC实现响应式编程
什么是响应式编程?
例,我们有3个变量,b = 3,c = 4, a = b + c = 7,如果这时我们修改b的值,让b=100,这时a的值不会发生变化,响应式编程就是当修改b或c的值时,a的值也会跟着变化。
在iOS开发中,可以使用
KVO 监听对象的属性值,达到这一效果!
因为苹果的 KVO会统一调用同一个方法,方法是固定的,如果监听属性过多,方法非常难以维护!
RAC是目前实现响应式编程的唯一解决方案!
MVVM:将“数据模型数据双向绑定”的思想作为核心,因此在View和Model之间没有联系,通过ViewModel进行交互,而且Model和ViewModel之间的交互是双向的,因此视图的数据的变化会同时修改数据源,而数据源数据的变化也会立即反应到View上。
代码实现
__weaktypeof(self)weakSelf
= self;
//双向绑定
// 1>模型(KVO数据)绑定
UI(text属性)
// a) name(string) -> text(string)
RAC(nameTextField,text)
= RACObserve(_person,name);
NSLog(@"RACObserve(_person,
name):%@",RACObserve(_person,name));
// b) age(NSInteger) -> text(string),RAC中传递的数据都是
id 类型
//如果使用基本数据类型绑定
UI的内容,需要使用 map函数,通过
block对 value的数值进行转换之后,才能绑定
RAC(ageTextField,text)
= [RACObserve(_person,age)map:^id_Nullable(id _Nullable
value) {
NSLog(@"%@
%@",value,[valueclass]);
//错误的转换,value本身已经是
NSNumber,需要字符串
// return [NSString stringWithFormat:@"%zd",value];
return
[valuedescription];
}];
// 2> UI绑定模型
[[RACSignalcombineLatest:@[nameTextField.rac_textSignal,ageTextField.rac_textSignal]]subscribeNext:^(RACTuple
* _Nullable x) {
weakSelf.person.name
= [x first];
weakSelf.person.age
= [[x second]integerValue];
}];
// 3>添加按钮,输出结果
UIButton
*btn = [UIButtonbuttonWithType:UIButtonTypeContactAdd];
btn.center
= self.view.center;
[self.viewaddSubview:btn];
[[btnrac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(__kindofUIControl
*_Nullable x) {
//循环引用!!!
NSLog(@"_person.name:%@
_person.age:%zd",weakSelf.person.name,weakSelf.person.age);
}];
}