RAC简介
一. 什么是ReactiveCocoa?
ReactiveCocoa(其简称为RAC)是由Github 开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程(FP)和响应式编程(RP)的特性。它主要吸取了.Net的 Reactive Extensions的设计和实现。
ReactiveCocoa 的宗旨是Streams of values over time ,随着时间变化而不断流动的数据流。
ReactiveCocoa 主要解决了以下这些问题:
-
UI数据绑定
- UI控件通常需要绑定一个事件,RAC可以很方便的绑定任何数据流到控件上。
-
用户交互事件绑定
- RAC为可交互的UI控件提供了一系列能发送Signal信号的方法。这些数据流会在用户交互中相互传递。
-
解决状态以及状态之间依赖过多的问题
- 有了RAC的绑定之后,可以不用在关心各种复杂的状态,isSelect,isFinish……也解决了这些状态在后期很难维护的问题。
-
消息传递机制的大统一
- OC中编程原来消息传递机制有以下几种:Delegate,Block Callback,Target-Action,Timers,KVO,objc上有一篇关于OC中这5种消息传递方式改如何选择的文章Communication Patterns,推荐大家阅读。现在有了RAC之后,以上这5种方式都可以统一用RAC来处理。 其实也就是一下的这些消息传递机制。RAC都用了统一的方式封装起来让我们更加方便的去使用。
RAC的常用方法
1.通知NSNotificationCenter
- 普通写法
- 添加键盘弹出的通知addObserver
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
- 实现方法
- (void) keyboardWillShow:(NSNotification *)note { NSLog(@"键盘弹出了"); }
- 移除通知
- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; }
- RAC 写法
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) { NSLog(@"%@",x); }];
- x的打印结果
NSConcreteNotification 0x6000000c5170 {name = UIKeyboardWillShowNotification; userInfo = { UIKeyboardAnimationCurveUserInfoKey = 7; UIKeyboardAnimationDurationUserInfoKey = "0.25"; UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {390, 336}}"; UIKeyboardCenterBeginUserInfoKey = "NSPoint: {195, 1012}"; UIKeyboardCenterEndUserInfoKey = "NSPoint: {195, 676}"; UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 844}, {390, 336}}"; UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 508}, {390, 336}}"; UIKeyboardIsLocalUserInfoKey = 1; }}
2.KVO
-
普通写法
- 模型
@interface TestModel : NSObject @property (assign, nonatomic) int age; @property (assign, nonatomic) int height; @end
#import "KVOViewController.h" #import "TestModel.h" @interface KVOViewController () @property (strong, nonatomic) TestModel *testModel1; @end @implementation KVOViewController - (void)viewDidLoad { [super viewDidLoad]; self.testModel1 = [[TestModel alloc] init]; self.testModel1.age = 1; self.testModel1.height = 11; // 给testModel1对象添加KVO监听 当KVO监听age属性变化 NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.testModel1 addObserver:self forKeyPath:@"age" options:options context:@"123"]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { self.testModel1.age = 20; self.testModel1.height = 30; } // 当监听对象的属性值发生改变时,就会调用 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context); } - (void)dealloc { [self.testModel1 removeObserver:self forKeyPath:@"age"]; [self.testModel1 removeObserver:self forKeyPath:@"height"]; }
- RAC写法 #define RACObserve(TARGET, KEYPATH) _RACObserve(TARGET, KEYPATH)
[RACObserve(self.testModel1, age)subscribeNext:^(id _Nullable x) { NSLog(@"%@",x); }];
3.代理 Delegate
- 普通写法
1. 遵守代理 <UITextFieldDelegate>
2. 空间 @property (weak, nonatomic) IBOutlet UITextField *textField;
3. 代理的响应者 self.textField.delegate = self;
4.代理方法
- (void)textFieldDidBeginEditing:(UITextField *)textField {
NSLog(@"开始编辑");
}
- RAC写法
self.textField.delegate = self;
[[self rac_signalForSelector:@selector(textFieldDidBeginEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"%@",x);
}];
<RACTuple: 0x600000dd9130> (
"<UITextField: 0x127d0bf90; frame = (97 118; 220 34); text = ''; opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000001e6f70>; borderStyle = RoundedRect; background = <_UITextFieldSystemBackgroundProvider: 0x600000f8eb80: backgroundView=<_UITextFieldRoundedRectBackgroundViewNeue: 0x127d0e710; frame = (0 0; 220 34); opaque = NO; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x600000f8ec60>>, fillColor=<UIDynamicModifiedColor: 0x6000001874e0; contrast = normal, baseColor = <UIDynamicSystemColor: 0x600001a9bb40; name = systemRedColor>>, textfield=<UITextField 0x127d0bf90>>; layer = <CALayer: 0x600000fe3ee0>>"
)
4.Target-Action
- 普通写法
[self.button addTarget:self action:@selector(Click:) forControlEvents:UIControlEventTouchUpInside];
- (void)Click:(UIButton *)sender {
NSLog(@"按钮被点击");
}
- RAC
[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"%@",x);
}];
<UIButton: 0x15a50d9f0; frame = (101 240; 213 205); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x60000262fb60>>
5.UITextField 输入文本UITextField的文本监听
- RAC 写法:
[self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
NSLog(@"%@",x);
}];
当在self.textField输入文字h时,会实时打印出变化后的文字
2019-05-16 18:02:58.562309+0800 001---RAC[54530:4536864] h
2019-05-16 18:02:59.049225+0800 001---RAC[54530:4536864] hh
2019-05-16 18:02:59.288995+0800 001---RAC[54530:4536864] hhh
6.手势
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
self.label.userInteractionEnabled = YES;
[self.label addGestureRecognizer:tap];
[tap.rac_gestureSignal subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
NSLog(@"%@",x);
}];
7、数组和字典的遍历
- 数组
TestModel *model1 = [[TestModel alloc] init];
model1.age = 1;
model1.height = 11;
TestModel *model2 = [[TestModel alloc] init];
model2.age = 2;
model2.height = 22;
TestModel *model3 = [[TestModel alloc] init];
model3.age = 3;
model3.height = 33;
NSArray *array = @[model1,model2,model3];
[array.rac_sequence.signal subscribeNext:^(TestModel * _Nullable x) {
NSLog(@"%d",x.age);
}];
- 字典
NSDictionary *dict = @{@"name":@"凡几多",@"age":@"20",@"sex":@"男"};
[dict.rac_sequence.signal subscribeNext:^(id _Nullable x) {
//元祖
NSLog(@"%@",x);
RACTwoTuple *tuple = (RACTwoTuple *)x;
NSLog(@"key == %@ , value = %@",tuple[0],tuple[1]);
}];
8、计时器
- NSTimer
NSTimer *timer = [NSTimer timerWithTimeInterval:30.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; // 需要加入手动RunLoop,需要注意的是在NSTimer工作期间self是被强引用的
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; // 使用NSRunLoopCommonModes才能保证RunLoop切换模式时,NSTimer能正常工作。
停止计时器
- (void)stopTimer {
if (_timer) {
[_timer invalidate];
}
}
- RAC
- 主线程
[[RACSignal interval:2.0 onScheduler:[RACScheduler mainThreadScheduler]]subscribeNext:^(NSDate * _Nullable x) {
NSLog(@"%@",x);
NSLog(@"%@",[NSThread currentThread]);
}];
- 子线程
[[RACSignal interval:2.0 onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh name:@"abc"]] subscribeNext:^(NSDate * _Nullable x) {
NSLog(@"%@",[NSThread currentThread]);
}];
拓展
- 数组便利是先执行Block 里面的在执行 block 外面的语句 参考文章 利用栅栏函数完成任务的调度
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
TestModel *model1 = [[TestModel alloc] init];
model1.age = 1;
model1.height = 11;
TestModel *model2 = [[TestModel alloc] init];
model2.age = 2;
model2.height = 22;
TestModel *model3 = [[TestModel alloc] init];
model3.age = 3;
model3.height = 33;
NSArray *array = @[model1,model2,model3];
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"🔞 START: %@", [NSThread currentThread]);
dispatch_async(concurrentQueue, ^{
[array.rac_sequence.signal subscribeNext:^(TestModel * _Nullable x) {
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%d",x.age);
}];
}); // ⬅️ 任务一
dispatch_barrier_async(concurrentQueue, ^{ sleep(3); NSLog(@"🚥🚥 %@", [NSThread currentThread]);}); // ⬅️ Barrie 任务
dispatch_async(concurrentQueue, ^{ NSLog(@"111");}); // ⬅️ 任务四
NSLog(@"🔞 END: %@", [NSThread currentThread]);
}
RAC高阶函数案例
- 1.信号映射:map与flattenMap
- 2.信号过滤:filter、ignore、distinctUntilChanged
- 3.信号合并:combintLatest、reduce、merge、zipWidth
- 4.信号连接:concat、then
- 5.信号操作时间:timeout、interval、dely
- 6.信号取值:take、takeLast、takeUntil
- 7.信号跳过:skip
- 8.信号发送顺序:donext、cocompleted
- 9.获取信号中的信号:switchToLatest
- 10.信号错误重试:retry
RAC高阶函数使用案例和场景
信号映射 flattenMap
的使用
- 1.flattenMap的场景:比如我输入一个号码,需要在前面+86.
// 映射
// 映射它的本质就是一种对应关系。
[[self.textField.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
NSLog(@"textField text is %@",value); // 输入的value值
return [RACSignal return:[NSString stringWithFormat:@"+86%@",value]]; // 通过拼接 返回值 传递到x里面
}] subscribeNext:^(id _Nullable x) {
NSLog(@"result is : %@",x); // 最终结果
}];
信号过滤 filter
的使用
- filter的场景:比如进行一个昵称长度进行比较。
// 过滤
[[self.textField.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
if (self.textField.text.length > 6) {
self.textField.text = [self.textField.text substringToIndex:6];
self.label.text = @"太长了";
// 如果字符串大于6个 将信息显示到 标签label 里面进行提示
}
return value.length < 6; // 如果字符没有大于6个 正常操作
}] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"过滤 --- %@",x); // 如果大于6个的话 这里不会执行
}];
信号合并(组合内聚) combineLatestWith
,combineLatest
的使用
-
combineLatestWith、combineLatest的场景 : 比如进行一个表格提交。例如注册账号。输入的内容有
姓名
,年龄
,地区
,性别
,简介
等输入信息进行注册。那么就需要用到组合。将所有关联的信号进行组合。然后进行聚合输出一个结果。提交到服务端 -
combineLatestWith
组合信号
RACSignal *sigalA = self.textField.rac_textSignal;
RACSignal *sigalB = [self.button rac_signalForControlEvents:UIControlEventTouchUpInside];
// 组合
RACSignal *comSig = [sigalA combineLatestWith:sigalB];
[comSig subscribeNext:^(id _Nullable x) {
NSLog(@"组合 %@",x);
}];
-
combineLatest
组合聚合
/**
组合内聚
1.先组合
2.再聚合
*/
RACSignal *sigalA = self.nameTextField.rac_textSignal;
RACSignal *sigalA1 = self.sexTextField.rac_textSignal;
RACSignal *sigalA2 = self.ageTextField.rac_textSignal;
RACSignal *sigalB = [self.saveBtn rac_signalForControlEvents:UIControlEventTouchUpInside];
[[RACSignal combineLatest:@[sigalA,sigalA1,sigalA2,sigalB] reduce:^id _Nullable(id valueA,id valueA1,id valueA2,id valueB){
// reduce 需要返回信号的值
// 也可以进行判断进行返回
return [NSString stringWithFormat:@"聚合 --- valueA is %@ valueA1 is %@ valueA2 is %@ valueB is %@",valueA,valueA1,valueA2,valueB];
}]subscribeNext:^(id _Nullable x) {
NSLog(@"聚合 --- x is %@",x); // 最终逻辑
}];
- 打印了三遍
信号连接 concat
- 信号相连,依次订阅到。要注意:前面的信号必须发送完成,才能接力到下一个信号。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"one"];
// [subscriber sendError:nil];
// 前面的信号必须发送完成,才能接力到下一个信号。
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signalA dispose");
}];
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"two"];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signalB dispose");
}];
}];
[[signalA concat:signalB] subscribeNext:^(id _Nullable x) {
NSLog(@"concat:%@", x);
}];
信号操作时间
timeout
**
NSLog(@"%s", __func__);
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
return [RACDisposable disposableWithBlock:^{
NSLog(@"signal dispose");
}];
}] timeout:5 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id _Nullable x) {
NSLog(@"timeOut:%@", x);
} error:^(NSError * _Nullable error) {
NSLog(@"timeOut-error:%@", error);
}];
输出:
20:09:29 -[ViewController testTimeOut]
20:09:29 signal dispose
20:09:34 timeOut-error:Error Domain=RACSignalErrorDomain Code=1 "(null)"
interval
[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(NSDate * _Nullable x) {
NSLog(@"interval:%@",[NSThread currentThread]);
}];
dely
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"1"];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signal dispose");
}];
}] delay:3] subscribeNext:^(id _Nullable x) {
NSLog(@"dely:%@", x);
}];
信号取值
take
屏蔽一些信号,取前 N 个信号。
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"1"];
[subscriber sendNext:@2];
[subscriber sendNext:@3];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signal dispose");
}];
}] take:2] subscribeNext:^(id _Nullable x) {
NSLog(@"take:%@", x);
}];
tekeLast
屏蔽一些信号,取后 N 个信号。注意:必须发送完成信号,才能确定最后的 N 个信号。
**
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"1"];
[subscriber sendNext:@2];
[subscriber sendNext:@3];
// 必须发送完成信号,才能确定最后的 N 个信号。
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signal dispose");
}];
}] takeLast:2] subscribeNext:^(id _Nullable x) {
NSLog(@"takeLast:%@", x);
}];
takeUntil
[A takeUntil:B]:B发出信号或者发出完成信号后,A的信号也就订阅不到了。
**
RACSubject *subjectA = [RACSubject subject];
RACSubject *subjectB = [RACSubject subject];
[[subjectA takeUntil:subjectB] subscribeNext:^(id _Nullable x) {
NSLog(@"takeUntil:%@", x);
}];
[subjectA sendNext:@"a-1"];
[subjectA sendNext:@"a-2"];
// [subjectB sendNext:@"b-2"];
[subjectA sendNext:@"a-3"];
[subjectA sendNext:@"a-4"];
[subjectB sendNext:@"b-1"];
[subjectA sendNext:@"a-5"];
信号跳跃
skip
跳过前 N 个信号。
**
[[self.tf.rac_textSignal skip:3] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"skip:%@", x);
}];
信号发送顺序
doNext
在源信号发送之前,可以在doNextBlock中做一些附加操作。
**
RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"signalB-1"];
[subscriber sendNext:@"signalB-2"];
[subscriber sendNext:@"signalB-3"];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signalB dispose");
}];
}];
[[signalB doNext:^(id _Nullable x) {
NSLog(@"do:%@", x);
}] subscribeNext:^(id _Nullable x) {
NSLog(@"订阅:%@", x);
}];
cocompleted
在源信号发送完成之前,可以在doCompletedBlock中做一些附加操作。 **
RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"signalB-1"];
[subscriber sendNext:@"signalB-2"];
[subscriber sendCompleted];
[subscriber sendNext:@"signalB-3"];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signalB dispose");
}];
}];
[[signalB doCompleted:^{
NSLog(@"do someting befor completed");
}] subscribeNext:^(id _Nullable x) {
NSLog(@"订阅到:%@", x);
}];
获取信号中的信号
switchToLatest
- 获取信号中信号最近发出信号,订阅最近发出的信号。
RACSubject *signal = [RACSubject subject];
RACSubject *signalOfSignals = [RACSubject subject];
[[signalOfSignals switchToLatest] subscribeNext:^(id _Nullable x) {
NSLog(@"switchToLatest:%@", x);
}];
// 注意switchToLatest:只能用于信号中的信号
[signalOfSignals sendNext:signal];
[signal sendNext:@"i am a signal"];
信号错误重试
retry
发送失败就会重新执行创建信号时的block,直至成功发送。
static int i = 0;
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
if (i == 5) {
[subscriber sendNext:[NSString stringWithFormat:@"%d", i]];
} else {
[subscriber sendError:nil];
}
i++;
return [RACDisposable disposableWithBlock:^{
NSLog(@"signal dispose");
}];
}] retry] subscribeNext:^(id _Nullable x) {
NSLog(@"next:%@", x);
} error:^(NSError * _Nullable error) {
NSLog(@"error:%@", error);
}];
replay
被 N 次订阅,则发送 N 遍信号。
RACSignal *signalA = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"signalA-1"];
[subscriber sendNext:@"signalA-2"];
[subscriber sendNext:@"signalA-3"];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signalA dispose");
}];
}] replay];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"replay-1:%@", x);
}];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"replay-2:%@", x);
}];