IOS RAC(Reactive Cocoa)
导入RAC 使用CocoaPods直接导入 pod 'ReactiveObjC'
万事万物皆信号,任何事情都是通过信号传递的
信号 => 订阅 响应式编程思想,只要信号已发生改变,就会通知订阅
RACSignal
RACSignal
:信号,ReactiveCocoa最基本类
RACDisposable
:处理数据,清空数据
block:什么时候执行,block能干嘛
创建一个基本的信号
// 创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber)
{
//<1>
//需要知道这个block:什么时候执行,block能干嘛
NSLog(@"RACSignal执行block");
// 网络请求
[subscriber sendNext:@3];
//<>
[subscriber sendCompleted];
//block 返回一个 RACDisposable对象
return [RACDisposable disposableWithBlock:^
{
// 当订阅者被销毁的时候就会执行
// 订阅发送完成或者error,也会执行Block
// 清空数据
NSLog(@"执行disposable");
}];
}];
RACSubscriber
:订阅者,发送信号消息
信号本身不具备发送消息能力
先订阅 在发送消息
订阅就会执行RACSignal的block
//需要了解信号什么时候发出,
//订阅信号
// 底层:创建订阅者
[signal subscribeNext:^(id _Nullable x)
{
NSLog(@"信号传值的时候 %@",x);
//<2>
}];
//订阅信号的错误<3>->执行Signal的block 如果有发送消息就会醒下面
[signal subscribeError:^(NSError * _Nullable error)
{
NSLog(@"error");
}];
//订阅信号的完成<4>
[signal subscribeCompleted:^
{
NSLog(@"completed");//<5>
}];
只要是订阅就会执行signal的block,所以不要订阅多次,因为只要重新订阅了,之前的订阅这就没有了,他会创建新的订阅者,如果我们想同时,定义next, error ,completed我们可以使用另一个方法
//注意点:不要分开订阅,要一起订阅.
[signal subscribeNext:^(id _Nullable x)
{
NSLog(@"next = %@", x);
}
error:^(NSError * _Nullable error)
{
}
completed:^
{
NSLog(@"订阅完成");
}];
RACSubject和RACReplaySubject
RACSubject可以用来代替代理的使用,他可以有多个订阅者, 而且多个订阅者都会被保存,
-
RACSubject和RACReplaySubject:
RACSubject即可以订阅,也可以发送消息
共同点:既可以充当信号也可以充当订阅者
开发中:一个数据,需要多个类同时处理,使用RACSubject
RACReplaySubject:保存值
RACSubject开发的时候,使用的比较多
RACSubject 代替代理 -
RACSubject 的基本使用
//多个订阅者
- (void)testRACSubject
{
// 先订阅 在发送信号
// 创建信号
RACSubject *subject = [RACSubject subject];
// 订阅
// 内部创建RACSubscriber,并且保存起来
// RACSubscriber保存nextBlock
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"第一个订阅者 %@", x);
}];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"第二个订阅者 %@", x);
}];
//发送信号,便利所有的订阅者
[subject sendNext:@5];
[subject sendNext:@3];
}
- RACReplaySubject基本使用
//先发信号,在订阅
- (void)testRACReplaySubject
{
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 发送信息
// 先保存123
// 在执行nextBlock
[replaySubject sendNext:@"nihao"];
[replaySubject sendNext:@3];
// 订阅信号
// 遍历值,让一个订阅者去发送多个值
// 只要订阅一次,之前所有发送的值都能获取到.
[replaySubject subscribeNext:^(id _Nullable x)
{
NSLog(@"%@",x);
}];
// 多个值一个类可以拿到
// 一个类想拿多个值,采用RACReplaySubject
// RACReplaySubject价值 :先发送,在订阅
// 热门 精选
}
RACSubject做代理使用
#import <UIKit/UIKit.h>
#import <ReactiveObjC/ReactiveObjC.h>
NS_ASSUME_NONNULL_BEGIN
@interface TestView : UIView
@property (nonatomic, strong) RACSubject *subject;
@end
NS_ASSUME_NONNULL_END
#import "TestView.h"
@implementation TestView
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"点击");
[self.subject sendNext:self];
}
@end
//使用
- (void)viewDidLoad
{
[super viewDidLoad];
TestView *redView = [[TestView alloc] initWithFrame:CGRectMake(20, 60, 100, 100)];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
RACSubject *oberver = [RACSubject subject];
[oberver subscribeNext:^(id _Nullable x)
{
NSLog(@"订阅%@", x);
}];
redView.subject = oberver;
}
RAC集合
在我们平时使用集合类型的时候,通常会涉及数据优化,开启一个异步线程去处理数据
RAC集合:异步线程处理数据
OC集合:数组,字典,NSSet
- rac遍历字典
- (void)dictToModel
{
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
NSArray *dicArr = [NSArray arrayWithContentsOfFile:filePath];
// 字典转模型
NSMutableArray *mutArr = [NSMutableArray array];
//1. 简单便利
[dicArr.rac_sequence.signal subscribeNext:^(id _Nullable x)
{
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"%@", x);
}];
//2. 更新UI,执行完成之后操作
[dicArr.rac_sequence.signal subscribeNext:^(id _Nullable x)
{
Item *item = [Item itemWithDict:x];
[mutArr addObject:item];
} completed:^
{ // 不管使用谁的框架,一定要记得确认线程,UI界面不显示,可能线程问题
NSLog(@"%@",mutArr);
NSLog(@"%@", [NSThread currentThread]);
}];
}
// 字典转模型
//3. map:映射
// mapBlock: value参数:集合 返回值:需要映射成那个值
//映射完成生成新的模型数组
mutArr = [[dicArr.rac_sequence map:^id _Nullable(id _Nullable value)
{
NSLog(@"%@", value);
NSLog(@"%@", [NSThread currentThread]);
return [Item itemWithDict:value];
}] array];
NSLog(@"%@", mutArr);
- RAC 字典的使用
///便利字典
- (void)dictTest
{
NSDictionary *dic =
@{
@"name":@"老王",
@"age":@"18",
@"height":@"198.0"
};
[dic.rac_sequence.signal subscribeNext:^(id _Nullable x)
{
//RAC有个宏可以 把快速元组解析出来
//x的第0个元素是key,1个是代表值
RACTupleUnpack(NSString *key ,id value) = x;
NSLog(@"key = %@ ,v = %@",key, value);
NSLog(@"%@", x[1]);
}];
}
RACMulticastConnection的使用
- (void)viewDidLoad
{
[super viewDidLoad];
// Multicast:广播连接
// RACMulticastConnection:解决RACSignal副作用
// 假如我们有两个地方都需要监听信号,但是信号里有个网络下载链接,因为RACSignal只有有订阅就会执行block所有,会照成下载方法多次执行的副作用,所有这里就引入了RACMulticast方法
// 网络请求:请求一次
@weakify(self);
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber)
{
@strongify(self)
NSLog(@"执行下载");
//以为block中使用了self,所有我们这里要进行指针的转化
[self downloadData:^(id data) {
[subscriber sendNext:data];
}];
return [RACDisposable disposableWithBlock:^{
}];
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"接受到数据%@", x);
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"接受到数据%@", x);
}];
}
- (void)downloadData:(void (^)(id))block
{
block(@"完成");
}
上面的例子,每当我们订阅信号,他就会执行一次下载操作,所以这是一个副作用。
我们使用RACMulticast就不可以解决这个问题,其实就是将signal转成RACMulticastConnection
// 网络请求:请求一次
@weakify(self);
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber)
{
@strongify(self)
NSLog(@"执行下载");
//以为block中使用了self,所有我们这里要进行指针的转化
[self downloadData:^(id data) {
[subscriber sendNext:data];
}];
return [RACDisposable disposableWithBlock:^{
}];
}];
RACMulticastConnection *conection = [signal publish];
[conection.signal subscribeNext:^(id _Nullable x)
{
NSLog(@"接受到数据%@", x);
}];
[conection.signal subscribeNext:^(id _Nullable x)
{
NSLog(@"接受到数据%@", x);
}];
// 进行连接
/*
RACSubject订阅signal
*/
[conection connect];
}
RACCommand
RAC中处理事件的类,可以把事件的处理,事件中的数据如何传递包装到这个类中,他可以很方便的监控事件的执行过程
比如 按钮的点击,网络请求
- 订阅command内部的信号
-(void)test
{
//处理事件
self.view.backgroundColor = [UIColor whiteColor];
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input)
{
NSLog(@"执行了command的block");
NSLog(@"%@", input);
//必须返回一个部位空的信号
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber)
{
//当我们执行了command的事件之后,信号就被触发,证明他在内部被订阅了
NSLog(@"执行了signal的block");
//发送数据 找到谁来接受这个信号。
//因为执行execute的时候会有一个信号的返回值,所以我们这个信号可以自己订阅
[subscriber sendNext:@"发出信号111"];
[subscriber sendCompleted];
return nil;
}];
}];
// 执行事件
[[command execute:@3] subscribeNext:^(id _Nullable x) {
NSLog(@"信号自己订阅%@", x);
}];
}
- 注意:
- RACCommand内部必须要返回signal
- executionSignals 信号中信号,一开始获取不到内部信号
2.1 switchToLatest:获取内部信号
2.2 execute:获取内部信号 - executing: 判断是否正在执行
3.1 第一次不准确,需要skip,跳过
3.2 一定要记得sendCompleted,否则永远不会执行完成 - execute执行,执行command的block
//处理事件
self.view.backgroundColor = [UIColor whiteColor];
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input)
{
NSLog(@"执行了command的block");
NSLog(@"%@", input);
//必须返回一个部位空的信号
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber)
{
//当我们执行了command的事件之后,信号就被触发,证明他在内部被订阅了
NSLog(@"执行了signal的block");
//发送数据 找到谁来接受这个信号。
//因为执行execute的时候会有一个信号的返回值,所以我们这个信号可以自己订阅
[subscriber sendNext:@"发出信号111"];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
//释放资源
}];
}];
}];
//executionSignals:信号中的信号
/*
[command.executionSignals subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);//这里传过来的值,就是 command返回的信号,所以我们可以在这里订阅信号
[x subscribeNext:^(id _Nullable x) {
NSLog(@"正在执行的信号%@",x);
}];
}];
*/
//另一种写法,可以直接订阅,最后一次发出的信号
[command.executionSignals.switchToLatest subscribeNext:^(id _Nullable x)
{
NSLog(@"最后一次发出的信号%@", x);
}];
//判断当前的信号是不是在执行,比如点击按钮下载东西
//监听命令的执行情况
[[command.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
BOOL isExecuting = [x boolValue];
if (isExecuting )
{
NSLog(@"正在执行");
}
else
{
NSLog(@"执行完毕");
}
}];
//执行事件
[command execute:@545];
-RACCommand的常用场景
监听按钮点击,网络请求
- 监听按钮的点击
//方式1
-(void)test
{
self.loginBtn.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input)
{
NSLog(@"btnClick%@", input);
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"登录"];
[subscriber sendCompleted];
return nil;
}];
}];
//监听
[self.loginBtn.rac_command.executionSignals.switchToLatest subscribeNext:^(id _Nullable x)
{
NSLog(@"%@", x);
}];
}
- 监听按钮的点击,并判断状态
//方式2
RACSubject *enableSignal = [RACSubject subject];
self.loginBtn.rac_command = [[RACCommand alloc] initWithEnabled:enableSignal signalBlock:^RACSignal * _Nonnull(id _Nullable input)
{
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"登录action"];
//发送执行完毕的信号
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendCompleted];
});
return nil;
}];
}];
[[self.loginBtn.rac_command.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
BOOL isLoging = [x boolValue];
//在没有完成之前按钮不可用
[enableSignal sendNext:@(!isLoging)];
}];