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
[
RACSignal
createSignal
:^
RACDisposable
*
_Nullable
(
id
<
RACSubscriber
>
_Nonnull
subscriber) {
//
发送不同的信号
_personList
= [
NSMutableArray
array
];
//
模拟异步加载数据
dispatch_async
(
dispatch_get_global_queue
(
0
,
0
), ^{
//
模拟延时
[
NSThread
sleepForTimeInterval
:
1.0
];
//
创建数据
for
(
NSInteger
i =
0
; i <
20
; i++) {
XZPerson
*person = [[
XZPerson
alloc
]
init
];
person.
name
= [
@"zhangsan - "
stringByAppendingFormat
:
@"%ld"
,(
long
)i];
person.
age
=
15
+
arc4random_uniform
(
20
);
[
_personList
addObject
:person];
}
NSLog
(
@"%@"
,
_personList
);
//
完成回调发送信号给订阅者,主线程
dispatch_async
(
dispatch_get_main_queue
(), ^{
BOOL
isError =
NO
;
if
(isError) {
[subscriber
sendError
:[
NSError
errorWithDomain
:
@"cn.xzproject.error"
code
:
1001
userInfo
:
@{
@"error message"
:
@"
异常错误
"
}
]];
}
else
{
[subscriber
sendNext
:
_personList
];
}
//
发送完成事件
[subscriber
sendCompleted
];
});
});
return
nil
;
}];
}
ViewController.m
- (
void
)loadData {
// 1.
实例化视图模型
_personListModel
= [[
XZPersonListModel
alloc
]
init
];
// 2.
加载数据
/**
next
是接收到数据
error
接收到错误,错误处理
completed
信号完成
*/
[[
_personListModel
loadPersons
]
subscribeNext
:^(
id
_Nullable
x) {
NSLog
(
@"==============%@"
,x);
//
刷新数据
[
self
.
tableNotice
reloadData
];
}
error
:^(
NSError
*
_Nullable
error) {
NSLog
(
@"==============%@"
,error);
}
completed
:^{
NSLog
(
@"==============
完成
"
);
}];
}
RAC-监听按钮和输入框事件
可以查看框架中的各种方法,RAC封装了常用的UI控件方法,需要监听哪个控件,查看一下这个控件或者他的父类的方法即可。
因为UIButton是继承自UIControl的,UIButton分类中没有合适的方法,所以,可以使用UIControl分类的方法
监听按钮的事件
-
不再需要新建一个方法,在
block
里面实现相应事件,
用block把重构的相关代码放在一起
[[btn
rac_signalForControlEvents
:
UIControlEventTouchUpInside
]
subscribeNext
:^(
__kindof
UIControl
*
_Nullable
x) {
NSLog
(
@"%@---------%@"
,x,[x
class
]);
}];
// [btn rac_signalForControlEvents:UIControlEventTouchUpInside]
是创建了一个冷信号,调用
subscribeNext
才订阅了信号,才会工作
监听文本输入框内容
-
参数就是输入的文本内容!
[[nameTextField
rac_textSignal
]
subscribeNext
:^(
NSString
*
_Nullable
x) {
NSLog
(
@"%@ %@"
,x,[x
class
]);
}];
组合信号
创建两个UITextField,将这两个的输入框的信号进行组合
UITextField
*nameTextField = [[
UITextField
alloc
]
initWithFrame
:
CGRectMake
(
20
,
40
,
300
,
40
)];
nameTextField.
borderStyle
=
UITextBorderStyleRoundedRect
;
[
self
.
view
addSubview
:nameTextField];
UITextField
*pwdTextField = [[
UITextField
alloc
]
initWithFrame
:
CGRectMake
(
20
,
100
,
300
,
40
)];
pwdTextField.
borderStyle
=
UITextBorderStyleRoundedRect
;
[
self
.
view
addSubview
:pwdTextField];
//
组合信号
Tuple
是元组
,
[[
RACSignal
combineLatest
:
@[
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,[x
class
]);
//
打印结果
===name
:
Wertyui pwd
:
3456yui [x class]:RACTuple
}];
// reduce ->
减少的意思,合并两个信号的数据,进行汇总计算时使用的!
// id
是返回值
,参数是有括号的;
// reduce
中,可以通过接收的参数进行计算,并且返回需要的数值!例:登录界面,只有用户名和密码同时存在,才允许登录!
//
方法一:使用
__weak
避免循环引用
// __weak typeof(self)weakSelf = self;
//
方法二:
@
weakify
(
self
);
[[
RACSignal
combineLatest
:
@[
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上。
代码实现
__weak
typeof
(
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,[value
class
]);
//
错误的转换,
value
本身已经是
NSNumber,
需要字符串
// return [NSString stringWithFormat:@"%zd",value];
return
[value
description
];
}];
// 2> UI
绑定
模型
[[
RACSignal
combineLatest
:
@[
nameTextField.
rac_textSignal
,ageTextField.
rac_textSignal
]
]
subscribeNext
:^(
RACTuple
*
_Nullable
x) {
weakSelf.
person
.
name
= [x
first
];
weakSelf.
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
:^(
__kindof
UIControl
*
_Nullable
x) {
//
循环引用!!!
NSLog
(
@"_person.name:%@ _person.age:%zd"
,weakSelf.
person
.
name
,weakSelf.
person
.
age
);
}];
}