公司项目中使用到了 RAC 的东西,现在有时间了稍微总结下:
一.什么是 RAC?
- 1.是有 github 开源的一个应用于 iOS 和 OS x 开发的新框架
- 2.RAC 是一个函数响应式编程框架
- 3.RAC 使用 signal 信号 和 signalProducer 信号提供者类型来便是事件流,也就是使用信号代替事件流
- 4.RAC 把所有的事件流统一用一种凡是来处理
- 代理,block,通知,点击事件和响应者链条事件,kvo,属性监听等
- 5.RAC 可以很简单的声明一个链条结合在一起,减少代码的耦合和相互之间的依赖
RAC 解决哪些问题?
- 1.是代码更加直观,减少代码的复杂度
- 2.使开发者专注于数据和界面
- 3.通过 block 减低代码间的耦合
- 4.为 target-action/代理/通知/kvo 统一消息传递机制
二.初识 RAC 的几个类
- 1.RACStream:一系列事件流的抽象类
- 2.RACSignal:具体的事件流对象,继承自 RACStream
- 3.RACDisposable:信号取消订阅对象
其他:RAC 为 UIKit 中的类大部分提供了分类,用来获取对象的信号
三.RAC 的使用思想
- 1.获取或者创建一个信号对象
- 2.调用信号的订阅方法
//监听用户密码文本变化 //RAC吧代理转化为文本信号 RACSignal *textSignal = self.pwdTF.rac_textSignal; [textSignal subscribeNext:^(id x) { NSLog(@"%@",x); }]; //监听按钮的点击事件 RACSignal *clickSignal = [self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside]; [clickSignal subscribeNext:^(id x) { NSLog(@"%@",x);//x是btn }];
- (void)viewDidLoad { [super viewDidLoad]; //subscribeNext方法会返回一个取消订阅对象 self.disposable = [[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(id x) { NSLog(@"%@",x);//x是btn }]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//点击空白部分后就不再订阅对应的信号 //取消按钮点击信号的订阅 [self.disposable dispose]; }
四.RACSignal 的其他方法
- 1.map:方法更改信号的输出类型,map 方法返回一个信号
//map:更改订阅block输出的id参数的类型 RACSignal *textSignal = self.pwdTF.rac_textSignal; textSignal = [textSignal map:^id(id value) {//这个value就是文本框输入的内容,可以在这个block做一些处理 return @(1); }]; [textSignal subscribeNext:^(id x) { NSLog(@"%@",x);//不管文本框输入什么,都输出1 }];
- 2.filter:过滤,filter 可以决定订阅的 block 是否被调用,方法返回一个信号
//filter可以决定订阅的block是否被调用,做一个过滤 RACSignal *textSignal = self.pwdTF.rac_textSignal; textSignal = [textSignal filter:^BOOL(NSString *value) { //当文字的个数大于6时,能调用订阅的block return value.length > 6; }]; [textSignal subscribeNext:^(id x) { NSLog(@"%@",x);//是否输出由filter返回的bool值决定 }];
- 3.combineLatest:reduce 方法可以组合信号
NSArray *signals = @[self.accountTF.rac_textSignal,self.pwdTF.rac_textSignal]; //第一个参数:信号的数组 第二个参数block //组合信号 RACSignal *combineSignal = [RACSignal combineLatest:signals reduce:^id(NSString *username,NSString *pwd){ //当用户名和密码长度都大于1时,返回yes BOOL btnEnable = (username.length > 0 && pwd.length > 0); return @(btnEnable); }]; [combineSignal subscribeNext:^(id x) { NSLog(@"%@",x);//这里订阅的是组合信号 //设置按钮的可用状态 self.loginBtn.enabled = [x boolValue]; }];
四.自定义信号
自定义一个网络请求信号
//1.创建一个网络信号,把网络请求封装成一个信号,一般封装到viewmodel中 RACSignal *ipSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { //subscriber:订阅者 //写网络请求 // 1.创建一个请求对象 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@""]]; // 2.通过session对象创建一个任务 NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession]dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (!error) { NSLog(@"%@",response); //网络请求成功,调用订阅者的sendNext方法 [subscriber sendNext:response];//不会终止信号的订阅 [subscriber sendCompleted];//会终止信号的订阅,订阅的block就不会被调用 }else{ NSLog(@"%@",error); [subscriber sendError:error];//会终止信号的订阅,订阅的block就不会被调用 } }]; // 3.执行任务 [dataTask resume]; //返回一个取消订阅对象 return [RACDisposable disposableWithBlock:^{ //取消网络请求 [dataTask cancel]; }]; }]; // 2.订阅信号 [ipSignal subscribeNext:^(id x) { NSLog(@"网络请求成功%@",x); } error:^(NSError *error) { NSLog(@"网络请求失败%@",error); } completed:^{ NSLog(@"终止了信号的订阅"); }];
五.信号的创建和订阅底层剖析
RAC 中是函数响应式编程思想,一般是信号,然后订阅信号,如果不订阅信号,信号就不会被执行,底层是如何实现的呢?下面一步步剖析原码结合图,看看底层到底如何实现的.
1.创建一个信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"发出信号"]; return nil; }];
- 1.上面创建了一个信号,跳进
createSignal
方法,看里面做了什么,其实就是返回了一个RACDynamicSignal
动态的信号,然后将didSubscribe
(比如上面的网络请求信号 block)这个 block 在动态信号创建时传了进去
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { return [RACDynamicSignal createSignal:didSubscribe];//返回动态信号 }
- 2.跳进动态信号的创建方法
createSignal
如下,可以看到didSubscribe
这个 block 在动态信号创建时copy
给了RACDynamicSignal
动态信号的一个属性.
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { RACDynamicSignal *signal = [[self alloc] init]; signal->_didSubscribe = [didSubscribe copy]; return [signal setNameWithFormat:@"+createSignal:"]; }
2.订阅信号
[signal subscribeNext:^(id x) { NSLog(@"成功接收信号%@",x); } error:^(NSError *error) { NSLog(@"接收信号失败%@",error); } completed:^{ NSLog(@"接收信号完毕"); }];
- 1.上面我们是订阅了这个信号,现在看一下
subscribeNext
里面做了什么
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock { NSCParameterAssert(nextBlock != NULL); //创建了一个订阅者 RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL]; return [self subscribe:o]; }
- 2.从上面可以看到,如果订阅了这个信号,内部会创建一个
RACSubscriber
对象,然后在创建时将订阅后的 block,就是一开始订阅信号subscribeNext
这个 block 传进去,然后再看里面做了什么
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed { RACSubscriber *subscriber = [[self alloc] init]; subscriber->_next = [next copy]; subscriber->_error = [error copy]; subscriber->_completed = [completed copy]; return subscriber; }
- 3.可以看到,其实就是将一开始订阅信号
subscribeNext
这个 block,赋值给了subscriber
订阅者的一个属性,然后接着看 2 中return [self subscribe:o];
这里的 self 是那个动态的信号,然后调用了subscribe
方法,里面是做了什么如下
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { NSCParameterAssert(subscriber != nil); RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; //把订阅者给改了 subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; if (self.didSubscribe != NULL) { RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ RACDisposable *innerDisposable = self.didSubscribe(subscriber); [disposable addDisposable:innerDisposable]; }]; [disposable addDisposable:schedulingDisposable]; } return disposable; }
- 4.上面的方法一步一步来看,首先创建了一个
RACCompoundDisposable
跳进去看一下,发现就是创建了一个对象什么也没做,subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
这里创建了一个RACPassthroughSubscriber
并且把订阅者subscriber
,信号self
,取消订阅disposable
传了进去,里面做了什么如下
- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable { NSCParameterAssert(subscriber != nil); self = [super init]; if (self == nil) return nil; _innerSubscriber = subscriber; _signal = signal; _disposable = disposable; [self.innerSubscriber didSubscribeWithDisposable:self.disposable]; return self; }
- 5.可以看到把订阅者
subscriber
,信号self
,取消订阅disposable
分别传给了对象RACPassthroughSubscriber
的 3 个属性,然后再返回到 3 中看创建了RACPassthroughSubscriber
后做了什么即 3 中的代码
if (self.didSubscribe != NULL) {//信号里的block不为空 RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ RACDisposable *innerDisposable = self.didSubscribe(subscriber); [disposable addDisposable:innerDisposable]; }]; [disposable addDisposable:schedulingDisposable]; }
- 6.这里首先
[RACScheduler.subscriptionScheduler
调用了RACScheduler
的一个方法subscriptionScheduler
看里面做了什么如下
+ (instancetype)subscriptionScheduler { static dispatch_once_t onceToken; static RACScheduler *subscriptionScheduler; dispatch_once(&onceToken, ^{ subscriptionScheduler = [[RACSubscriptionScheduler alloc] init]; }); return subscriptionScheduler; }
- 7.创建了一个
RACSubscriptionScheduler
对象,然后接着看紧接着用创建出来的对象调用了schedule
方法,并且传进去一个 block,看下里面做了什么如下
- (RACDisposable *)schedule:(void (^)(void))block { NSCParameterAssert(block != NULL); if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block]; block();//调用block return nil; }
- 8.我们发现我们的 block 在这里执行,这个 block,是什么就是
RACDisposable *innerDisposable = self.didSubscribe(subscriber); [disposable addDisposable:innerDisposable]; }];
其中 self.didSubscribe(subscriber);
就是执行我们网络请求的那个 block,
- 综述:最终是 RACScheduler 来执行的,所以如果信号不被订阅就不会有
RACScheduler
这个类,信号就不会被执行
六.RAC 中常用方法
1.flatternMap:信号嵌套
场景设置:点击按钮后再发送网络请求,要监听这两个信号
[[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInSide] flatterMap:^RACStream *(id value){//返回一个新的信号对象 return [self ipSignal] ;//网络请求的信号 }] subscribeNext:^(id x){ NSLog(@"%@",x); }]
2. doNext:额外再添加一些代码
场景设置:点按钮转菊花
[[[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInSide]doNext:^(id x){ //网络请求之前做事情 [self.indicator startAnimating]; }]flatterMap:^RACStream *(id value){//返回一个新的信号对象 return [self ipSignal] ;//网络请求的信号 }] subscribeNext:^(id x){ NSLog(@"%@",x); [self.indicator stopAnimating]; }]
3.throttle:订阅的 block(subscribeNext)延时被调用
场景设置:点击按钮一段时间后再调用
[[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] throttle:5]subscribeNext:^(id x){ NSLog(@"%@",x); }]
4.concat :信号串联,必须是一个信号完成之后,才可以开始另一个信号,所以第一个信号必须执行 sendCompleted 才会执行后面的信号
5. merge: 信号并联,信号可以同时并联执行,必定是多线程执行的,后面的信号不一定等到前面的信号执行完才执行
RACSignal *eat = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"吃饭"]; [subscriber sendNext:@"完毕"]; [subscriber sendCompleted]; return nil; }]; RACSignal *work = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"上班"]; [subscriber sendNext:@"完毕"]; [subscriber sendCompleted]; return nil; }]; [[eat concat:work]subscribeNext:^(id x) { NSLog(@"%@",x);//吃饭 完毕 上班 完毕 eat没有sendCompleted,就输出 吃饭 完毕 }]; [[eat merge:work]subscribeNext:^(id x) { NSLog(@"%@",x); }];
6.then:信号忽略,会忽略调用此方法的信号,内部实现是串联 concat
[[eat then:^RACSignal *{//返回一个信号 return work;//只执行上班, }] subscribeNext:^(id x) { NSLog(@"%@",x);//会忽略吃饭 }];;
7. delay:延迟多久后,订阅 block 才会被调用
[[[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside]delay:5]subscribeNext:^(id x) { NSLog(@"%@",x);//5s后调用 }];
8.timeout:超时
[[self.internationnalSignal timeout:5 onScheduler:[RACScheduler scheduler]]subscribeNext:^(id x) { NSLog(@"%@",x);//如果时间超过5s后就不会调用 }];
七.常用 RAC 中信号分类:
1. UIImagePickerController+RACSignalSupport.h:获取相册图片
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { UIImagePickerController *picker = [[UIImagePickerController alloc]init]; picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; //之前是设置代理 //现在通过信号获取用户选中的图片 穿过来的是一个字典 [picker.rac_imageSelectedSignal subscribeNext:^(NSDictionary * x) { NSLog(@"%@",x); UIImage *selectedImage = x[@"UIImagePickerControllerOriginalImage"]; [self dismissViewControllerAnimated:YES completion:nil]; }]; [self presentViewController:picker animated:YES completion:nil]; }
2. NSNotificationCenter+RACSupport.h:通知
[[[NSNotificationCenter defaultCenter]rac_addObserverForName:UIKeyboardWillShowNotification object:nil]subscribeNext:^(id x) { NSLog(@"%@",x); }];
3. "UIGestureRecognizer+RACSignalSupport.h":添加手势
self.view.userInteractionEnabled = YES; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]init]; [tap.rac_gestureSignal subscribeNext:^(id x) { NSLog(@"view被点击"); }]; [self.view addGestureRecognizer:tap];
八.RAC 中常用宏和循环引用
- 1.RACObserve(TARGET,KEYPATH):KVO 监听
//监听某个对象的属性是否变化 //方法一: [self.pwdTF.rac_textSignal subscribeNext:^(id x) { NSLog(@"%@",x); }]; //方法二:RACObserve(<#TARGET#>, <#KEYPATH#>) [RACObserve(self.pwdTF,text)subscribeNext:^(id x) { NSLog(@"%@",x); }];
- 2.RAC(TARGET, ...):绑定某个对象的属性
RAC(self.loginBtn.titleLabel,text) = self.pwdTF.rac_textSignal;
- 3.@weakify(self) @strongify(self) :解决 block 的循环应用
//解决block的循环应用 //方法一:_weak typeof(self) weakself = self; //方法二:@weakify(self) @strongify(self),必须意义对应的使用,可以批量生产弱引用 @weakify(self) [[RACSignal combineLatest:@[self.accountTF.rac_textSignal,self.pwdTF.rac_textSignal] reduce:^id(NSString *username,NSString *pwd){ return @(username.length > 0 && pwd.length > 0); }]subscribeNext:^(id x) { @strongify(self) self.loginBtn.enabled = [x boolValue]; }];
还有两个重要的类:
1.RACCommand
//监听按钮的点击 //方法一: [[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(id x) { NSLog(@"点击了按钮%@",x); }]; //方法二: //RACCommand:(命令):点击了按钮,执行RACCommand内部的RACSignal的订阅方法 self.loginBtn.rac_command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) { return [self ipSignal]; }]; //按钮的可用信号 RACSignal *btnEnableSignal = [RACSignal combineLatest:@[self.accountTF.rac_textSignal,self.pwdTF.rac_textSignal] reduce:^id(NSString *username,NSString *pwd){ return @(username.length>0 && pwd.length>0); }]; //按钮点击后触发信号 self.loginBtn.rac_command = [[RACCommand alloc]initWithEnabled:btnEnableSignal signalBlock:^RACSignal *(id input) { return [self ipSignal]; }];
2.RACSequence:主要用于遍历,继承自 RACStream
// RACSequence:主要用于遍历,继承自RACStream // 获取RACSequence里的信号,然后进行遍历 // 1.数组遍历 NSArray *names = @[@"liu",@"wen",@"xiao",@"li"]; [names.rac_sequence.signal subscribeNext:^(id x) { NSLog(@"%@",x); }]; //2.字符串遍历 NSString *text = @"123456789"; [text.rac_sequence.signal subscribeNext:^(id x) { NSLog(@"%@",x); }]; //3.添加过滤条件 [[text.rac_sequence.signal filter:^BOOL(id value) { return [value integerValue] > 5; }] subscribeNext:^(id x) { NSLog(@"%@",x);//输出6,7,8,9 }];
未完待续
由于时间问题,随后会写一个 MVVM 结合 RAC 的小项目供大家参考
最后:文章推荐
如果想深入学习请看这里
- 美团的技术博客
- ReactiveCocoa 与 Functional Reactive Programming
如果想从入门到进阶看这里 GitHub
如果想了解其他相关基础知识看这里最快让你上手 ReactiveCocoa
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于