Block
block类型:
//block: 就是找到block装起来的代码去执行
void(^block)(void) = ^{
NSLog(@"block封装的代码");
};
// 这个block的类型是 void(^)()
block();
使用
- (void)masonry
{
self.test(); //这里执行其实是执行block里面装的代码
}
//block做返回值
- (void(^)(void))test
{
void (^block)(void) = ^{
NSLog(@"执行这里的代码");
};
return block;
}
MVVM
目的: 将UI逻辑与业务逻辑分离
注意点:
- View引用ViewModel, 但反过来不行
- ViewModel引用Model, 但反过来不行
问题: 如果view引用了ViewModel, 但ViewModel没有引用View, 那么ViewModel如何更新视图呢?
响应式编程思想(KVO)
链式编程思想(block做参数, block返回对象本身)
ReactiveCocoa主要由以下四大核心组件构成
- 信号源: [RACStream]及其子类最核心
- 订阅者: [RACSubscriber]的实现类及其子类
- 调度器: [RACScheduler]及其子类
- 清洁工: [RACDisposable]及其子类
万物皆信号
RACSiganl
- 只有数据改变时, 信号内部才会发出数据, 它本身不具备发送信号的能力, 而是交给内部一个订阅者去发出
- 默认一个信号都是冷信号, 也就是值改变了,也不会触发, 只有订阅了这个信号, 这个信号才会变成热信号, 值改变才会触发
- 不要分开订阅next, error, completed这样的话会把前面的订阅者清除掉, 应该使用直接返回这三种的订阅方法
[requestSignal subscribeNext:^(id _Nullable x) {
NSLog(@"next的block");
} error:^(NSError * _Nullable error) {
NSLog(@"error的block");
} completed:^{
NSLog(@"completed的block");
}];
一个信号最好只订阅一次, 这样就没有反复创建订阅者的副作用
// RACSignal使用步骤:
// 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
// 2.订阅信号,才会激活信号. - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 3.发送信号 - (void)sendNext:(id)value
// RACSignal底层实现:
// 1.创建信号,首先把didSubscribe保存到信号中,还不会触发。
// 2.当信号被订阅,也就是调用signal的subscribeNext:nextBlock
// 2.2 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
// 2.1 subscribeNext内部会调用siganl的didSubscribe
// 3.siganl的didSubscribe中调用[subscriber sendNext:@1];
// 3.1 sendNext底层其实就是执行subscriber的nextBlock
// 1.创建信号
RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// block调用时刻:每当有订阅者订阅信号,就会调用block。
// 2.发送信号
[subscriber sendNext:@1];
// 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
// block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不在被订阅了。
NSLog(@"信号被销毁");
}];
}];
// 3.订阅信号,才会激活信号.
[siganl subscribeNext:^(id x) {
// block调用时刻:每当有信号发出数据,就会调用block.
NSLog(@"接收到数据:%@",x);
}];
RACSubscriber(订阅者), 用于发送信号, 这是一个协议不是一个类
RACDisposable用于取消订阅或者清理资源, 当信号发送完成或发送错误的时候, 就会自动触发它
RACSubject: 遵循了RACsubscriber协议的RACSignal,所以自己是信号, 也可以订阅其他信号
RACReplaySubject: RACSubject的子类, 重复提供信号, 先把发送过的信号缓存起来, 再次订阅时直接发送缓存的值
解释: 信号类RACSignal和RACSubject都遵守先订阅再发送信号原则, 但是RACReplaySubject可以先发送信号(内部是一个数组把信号保存起来), 再订阅, 因为它会把信号缓存起来
使用场景: 一个类要拿到多个值的时候
// RACSubject使用步骤
// 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
// 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 3.发送信号 sendNext:(id)value
// RACSubject:底层实现和RACSignal不一样。
// 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
// 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.订阅信号
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"第一个订阅者%@",x);
}];
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"第二个订阅者%@",x);
}];
// 3.发送信号
[subject sendNext:@"1"];
// RACReplaySubject使用步骤:
// 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
// 2.可以先订阅信号,也可以先发送信号。
// 2.1 订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 2.2 发送信号 sendNext:(id)value
// RACReplaySubject:底层实现和RACSubject不一样。
// 1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
// 2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock
// 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。
// 也就是先保存值,在订阅值。
// 1.创建信号
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 2.发送信号
[replaySubject sendNext:@1];
[replaySubject sendNext:@2];
// 3.订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"第一个订阅者接收到的数据%@",x);
}];
// 订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"第二个订阅者接收到的数据%@",x);
}];
RACTuple: 类似NSArray, 用来包装值
RACSequence: RAC中的集合类, 用于代替NSArray, NSDictionary, 可以使用它来快速来便利数组和字典
注意RACSequence会开启子线程, 要在completed做U更新
RACCommand: RAC中用于处理事件的类, 可以把事件如何处理, 事件中的数据如何传递, 包装到这个类中, 可以很方便的监控事件的执行过程
使用场景: 按钮点击, 网络请求
//RACCommand控制按钮点击
_nextBtn.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
//command的block
NSLog(@"点击了按钮---%@", input);
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//信号block
[subscriber sendNext:@"需要传出去的数据"];
return nil;
}];
}];
//监听
[_nextBtn.rac_command.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
RACCommand用来控制按钮是否可以点击
//用来控制能否点击的信号
RACSubject *enableSignal = [RACSubject subject];
_nextBtn.rac_command = [[RACCommand alloc] initWithEnabled:enableSignal signalBlock:^RACSignal * _Nonnull(id _Nullable input) {
//command的block
NSLog(@"点击了按钮---%@", input);
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//信号block
[subscriber sendNext:@"需要传出去的数据"];
return nil;
}];
}];
//发送控制信号
[[_nextBtn.rac_command.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
BOOL executing = [x boolValue];
[enableSignal sendNext:@(executing)];
}];
//监听
[_nextBtn.rac_command.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
硬性要求: 必须要有信号返回
/*RACCommand使用注意点
1, 必须返回一个信号
2, executionSignals: RACCommand返回的信号时信号中的信号, 有两种方式可以获取最新的信号
- 1,switchToLatest: 获取内部的最新信号(被动执行)
- 2,execute: 获取内部信号(这个是主动执行)
3, executing 用来判断是否正在执行, 第一次不准确需要跳过
4, 一定要记得sendCompleted, 否则永远不会执行完成
*/
// 一、RACCommand使用步骤:
// 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
// 2.在signalBlock中,创建RACSignal,并且作为signalBlock的返回值
// 3.执行命令 - (RACSignal *)execute:(id)input
// 二、RACCommand使用注意:
// 1.signalBlock必须要返回一个信号,不能传nil.
// 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
// 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
// 4.RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。
// 三、RACCommand设计思想:内部signalBlock为什么要返回一个信号,这个信号有什么用。
// 1.在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
// 2.当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。
// 四、如何拿到RACCommand中返回信号发出的数据。
// 1.RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
// 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
// 五、监听当前命令是否正在执行executing
// 六、使用场景,监听按钮点击,网络请求
// 1.创建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"执行命令");
// 创建空信号,必须返回信号
// return [RACSignal empty];
// 2.创建信号,用来传递数据
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"请求数据"];
// 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
[subscriber sendCompleted];
return nil;
}];
}];
// 强引用命令,不要被销毁,否则接收不到数据
_conmmand = command;
// 3.订阅RACCommand中的信号
[command.executionSignals subscribeNext:^(id x) {
[x subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}];
// RAC高级用法
// switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 4.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。
[[command.executing skip:1] subscribeNext:^(id x) {
if ([x boolValue] == YES) {
// 正在执行
NSLog(@"正在执行");
}else{
// 执行完成
NSLog(@"执行完成");
}
}];
// 5.执行命令
[self.conmmand execute:@1];
RACMulticastConnection:(多播)用于当一个信号, 被多次订阅时, 为保证创建信号时, 避免多次调用创建信号中的block, 造成副作用, 可以使用这个类处理
** 封装在底层, 开发中很少使用! 就是用来解决RACSignal的副作用(副作用就是不管你订阅多少次我就只会执行一次) **
使用
//创建多播
RACMulticastConnection *connection = [bookSignal publish];
//订阅多播
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"拿到多播数据");
}];
//千万不能忘记的进行连接
[connection connect];
RACScheduler: RAC中的队列, 用GCD封装的
RACUnit: 表示stream不包含有意义的值! 可以直接理解为nil
RACEvent: 把数据包装成信号事件
ReactiveCocoa开发中常见用法
- rac_signalForSelector: 替代代理
返回一个信号, 判断是否调用某个方法 - rac_valuesAndChangesForKeyPath: 监听某个对象的属性变化(KVO)
//注意:keypath(<#...#>) 的使用, 可以避免在观察者里面使用字符串, @keypath(self, age) == @"age"
[self rac_valuesForKeyPath:keypath(self, age) observer:self];
//移除观察者还需要使用OC的方法
- rac_signalForControlEvents: 监听某个事件
// 监听登录按钮点击
[[_nextBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
//执行登录事件
[self.loginVM.loginCommand execute:nil];
}];
- rac_addObserverForName: 监听某个通知
//监听通知
//不需要管理这个观察者, 不用去移除了
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"note" object:nil] subscribeNext:^(NSNotification * _Nullable x) {
NSLog(@"%@", x);
}];
//发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"note" object:@"Never"];
- rac_textSignal: 监听文本变化
- 一个界面多个请求的问题
//RAC处理一个界面多个接口的问题, 需求: 多个接口的最后结束时间
//Selector: 当数组所有信号都发送next后会执行选择器的方法(硬性参数: 选择器方法必须有参数, 而且参数个数和信号个数相等)
//<#(nonnull NSArray<RACSignal *> *)#> 请求数据的信号数组
self rac_liftSelector:@selector(@"updateUI") withSignalsFromArray:<#(nonnull NSArray<RACSignal *> *)#>
ReactiveCocoa常见宏
- RAC(TARGET, [KEYPATH, [NIL_VALUE]])[动态绑定, 属性对应右边的信号]
// 只要文本框文字改变,就会修改label的文字
RAC(self.labelView,text) = _textField.rac_textSignal;
- RACObserve(self, name): 监听某个对象的某个属性, 返回的是信号
[RACObserve(self.view, center) subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
- @weakify(Obj)和@strongify(Obj): 配套使用
- RACTuplePack: 把数据包装成RACTuple(元组类)
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@10,@20);
- RACTupleUnpack: 把RACTuple(元组类)解包成对应的数据
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@"xmg",@20);
// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
// name = @"xmg" age = @20
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
ReactiveCocoa---映射(用于把源信号内容, 映射成新内容)
- flattenMap---针对信号
//flattenMap
[[self.userName.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
// 在这里可以处理value
return [RACReturnSignal return:value]; //区别: 返回的是信号
}] subscribeNext:^(id _Nullable x) {
NSLog(@"flattenMap --- %@", x);
}];
- Map---针对值
//Map
[[self.userName.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
//在这里可以处理value
return value; //区别: 返回的是值
}] subscribeNext:^(id _Nullable x) {
NSLog(@"Map --- %@", x);
}];
ReactiveCocoa---组合
- concat---按顺序拼接信号(想按数据办事,用这个)
需求 - 有两个数据请求, 需要把两次数据请求的数据拼接到一个数组中(还是有顺序的)
//拼接concat
RACSubject *subjectA = [RACSubject subject];
RACSubject *subjectB = [RACSubject subject];
//会按顺序拼接, 必须要第一个信号发送完成, 第二个信号才能获取值
[[subjectA concat:subjectB] subscribeNext:^(id _Nullable x) {
NSLog(@"拼接后的值 --- %@", x);
}];
[subjectA sendNext:@"A"];
//只有第一个信号发送完成, 才会拿到第二个信号的值
[subjectA sendCompleted];
[subjectB sendNext:@"B"];
- then--- 用于连接两个信号, 当第一个信号完成, 才会连接then返回的信号
订阅多次出现的副作用
//then: 也是按顺序连接信号(都是等前面的信号完成, 再去订阅下一个信号), 和concat的区别是只能拿到最后一次信号的值
//作用: 可以用来处理副作用(保证创建一次), 如RACSignal中的信号创建的block会调用多次的问题
RACSignal *signaA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"测试创建次数A");
[subscriber sendNext:@"--A"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signaB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"测试创建次数B");
[subscriber sendNext:@"--B"];
return nil;
}];
[[signaA concat:signaB] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
[[signaA concat:signaB] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
//使用then(主要可以用来解决block嵌套不方便看的问题)
//then: 也是按顺序连接信号(都是等前面的信号完成, 再去订阅下一个信号), 和concat的区别是只能拿到最后一次信号的值
//作用: 可以用来处理副作用(保证创建一次), 如RACSignal中的信号创建的block会调用多次的问题
RACSignal *signaA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"测试创建次数A");
[subscriber sendNext:@"--A"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signaB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"测试创建次数B");
[subscriber sendNext:@"--B"];
return nil;
}];
//then: 拼接, 忽略掉上一个信号的值
[[signaA then:^RACSignal * _Nonnull{
return signaB;
}] subscribeNext:^(id _Nullable x) {
NSLog(@"then---%@", x);
}] ;
//then需求--- 第二次数据请求基于第一次数据请求的情况
//then 解决block嵌套问题
[self loadFirst:^(id data){
//请求详情
[self loadDetail:^(id data){
// block相互嵌套, 逻辑不好看
}];
}];
//可以看到步骤
[[self loadFirst] then:^RACSignal *{
return [self loadDetail];
}] then:^RACSignal *{
return [self loadOther];
}];
可以看到创建信号的block跑了两次
2017-12-01 14:24:09.314948+0800 QT[3456:400220] 测试创建次数A
2017-12-01 14:24:09.315390+0800 QT[3456:400220] --A
2017-12-01 14:24:09.315682+0800 QT[3456:400220] 测试创建次数B
2017-12-01 14:24:09.315925+0800 QT[3456:400220] --B
2017-12-01 14:24:09.316393+0800 QT[3456:400220] 测试创建次数A
2017-12-01 14:24:09.316828+0800 QT[3456:400220] --A
2017-12-01 14:24:09.317181+0800 QT[3456:400220] 测试创建次数B
2017-12-01 14:24:09.317479+0800 QT[3456:400220] --B
- marge
//marge: 不用前一个信号发送完成, 就可以拿到下一个信号的数据, 无顺序的
RACSubject *subjectX = [RACSubject subject];
RACSubject *subjectY = [RACSubject subject];
[[subjectX merge:subjectY] subscribeNext:^(id _Nullable x) {
NSLog(@"marge --- %@", x);
}];
[subjectX sendNext:@"X数据"];
[subjectY sendNext:@"Y数据"];
- zipWith---把两个信号压缩成一个信号
//zipWith: 不用前一个信号发送完成, 就可以拿到下一个信号的数据, 无顺序的
RACSubject *subjectX = [RACSubject subject];
RACSubject *subjectY = [RACSubject subject];
[[subjectX zipWith:subjectY] subscribeNext:^(id _Nullable x) {
//x的类型是一个元组 RACTuple
RACTupleUnpack(NSString *a, NSString *b) = x;
NSLog(@"zipWith --- %@", x);
}];
[subjectX sendNext:@"X数据"];
[subjectY sendNext:@"Y数据"];
- combineLatest---将多个信号合并起来用的最多
//这种用法只能处理两个信号
//combinLatest:合并
//reduce: 聚合
//需求: 用多个条件判断一个事件是否执行, 如登录按钮
RACSubject *subjectM = [RACSubject subject];
RACSubject *subjectN = [RACSubject subject];
[[subjectM combineLatestWith:subjectN] subscribeNext:^(id _Nullable x) {
//x - 是元组类型
NSLog(@"%@", x);
}];
[subjectM sendNext:@"数据M"];
[subjectN sendNext:@"数据N"];
//处理多个信号的用法
//combinLatest:合并(解决合并信号数超过2个的情况)
//reduce: 聚合(把多个信号的值聚合成一个值) 有参数的需要自己加, 几个信号就几个参数, 参数就是值
//需求: 用多个条件判断一个事件是否执行, 如登录按钮
RACSubject *subjectM = [RACSubject subject];
RACSubject *subjectN = [RACSubject subject];
[[RACSignal combineLatest:@[subjectM, subjectN] reduce:^id (NSString *m, NSString *n){
NSLog(@"m: %@ --- n: %@", m, n);
return @"返回值给下面的x";
}] subscribeNext:^(id _Nullable x) {
NSLog(@"x - %@ --- 拿到的就是reduce中return的值", x);
}];
[subjectM sendNext:@"数据M"];
[subjectN sendNext:@"数据N"];
ReactiveCocoa---过滤
- filter:--- 过滤信号, 获取满足条件的信号
//filter: 过滤
//需求: 如果密码的长度小于6就不处理
[[self.pwd.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
return value.length > 6;
}] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"filter --- %@", x);
}];
- ignore:--- 忽略某些值的信号
- distinctUntilChanged:--- 当值明显变化的时候就会发出信号,否则会被忽略掉(比如两次输入不一样的时候再发送, 做优化)
- take:---从开始取N次的信号
- takeLast:---取最后N次的信号
- takeUntil:---获取某个信号直到某个信号执行完成(到文本框销毁)
- skip:---跳过几个信号
- switchToLatest:---获取最新的信号
ReactiveCocoa---秩序
- doNext:---执行Next之前, 会先执行这个block
- doCompleted:---执行sendCompleted之前, 会先执行这个Block
ReactiveCocoa---线程
- deliverOn: 内容传递到指定线程中, 副作用在原来的线程中(把多次创建信号时block中的代码称之为副作用)
- subscribeOn: 内容和副作用都会传递到指定线程中
ReactiveCocoa---时间
- timeout:---超时,让一个信号在一定的事件后, 自动报错
- interval:---定时,每隔一段时间发出信号
//定时器
//interval: 隔多少秒发送
//RACScheduler: 线程类
[[RACSignal interval:3 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
NSLog(@"执行了定时器%@", x);
}];
- delay:--- 延迟, 延迟发送next
//delay
//延迟发送数据
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"要延迟发送的数据"];
return nil;
}] delay:2] subscribeNext:^(id _Nullable x) {
NSLog(@"拿到延迟发送的数据 --- %@", x);
}];
ReactiveCocoa---重复
- retry:---重试, 只要失败就会重新执行创建信号中的block,直到成功
- throttle:---节流 当某个信号发送比较频繁时, 可以使用节流, 在某一段时间不发送信号内容, 过一段时间获取信号的最新内容发出
注意点
- RACSignal和RACSubject一定要先订阅再发送数据
- 数据请求是最好是使用RACReplaySubject, 避免发送数据在订阅之前(RACCommand底层用的就RACReplaySubject)
- RACSubject更能避免RACSignal的副作用
网友评论