RAC(ReactiveCocoa) 知识总结

本贴最后更新于 2905 天前,其中的信息可能已经时移世易

公司项目中使用到了 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 中是函数响应式编程思想,一般是信号,然后订阅信号,如果不订阅信号,信号就不会被执行,底层是如何实现的呢?下面一步步剖析原码结合图,看看底层到底如何实现的.

Snip20161109_2.png

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);
}]

Snip20161108_9.png

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];
}]

Snip20161109_12.png

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 的小项目供大家参考

最后:文章推荐

如果想深入学习请看这里

  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    85 引用 • 139 回帖 • 1 关注
  • RAC
    1 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...