公司项目中使用到了 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
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于