走进ReactiveCocoa的世界
在学习ReactiveCocoa之前,先学习一下概念
函数式编程函数式编程,简单来说,就是多使用匿名函数,将逻辑处理过程,以一系列嵌套的函数调用来实现,以减少中间状态的存在。 简单举例,一个简单的表达式,(将表达式理解成一系列的逻辑处理流程): (1 + 2) * 3 - 4 如果是传统的过程式编程,则会这样写: a = 1 + 2; b = a * 3; c = b - 4; 而在函数式编程中,我们将运算过程(逻辑处理流程),定义为不同的函数,然后会写成: result = subtract(multiply(add(1,2),3),4) 从这里,我们就可以看出一个特点,过程式编程会在运行中,将一步步操作的结果以状态的形式纪录下来,下一步操作是修改上一步操作的结果。 而在函数式编程中,上一步操作的结果会直接以参数的形式或者其它形式传递给下一步操作,不再本地保存一堆无用的中间状态,而是输入一个初始值,就返回一个相应的结果。中间状态会互相影响,过多的中间状态会降低代码可读性以及提高维护的难度。通过函数式编程,减少状态的存在,一个操作,一个流程,只由输入值来决定输出结果,不在运行过程中以来全局状态或者保存中间状态。 所以函数式编程的主要优点就在于 不保存中间状态,缺点的话,为了不保存这个中间状态,而在函数间传递,会增加函数的调用次数,而这样会在一定程度上降低效率。 函数式编程的其它优点:
函数式编程,指尽量减少状态的保存,直接由输入得到结果,而不是在一些地方放置一堆的状态.即Model更新时,是直接作用于View,让View做出相应的显示,而不是保存一个状态,然后再通知View来获取这个状态.对于少量的状态,这样处理起来可能没问题,但是一旦状态多起来,管理就变得十分麻烦,难以调试.所以函数式编程,目的是 让相同的输入导出相同的输出,减少由于保存状态带来的影响. 响应式编程响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。 响应式编程对应的是命令式编程,命令式编程中,数据以状态的形式保存,通过命令来通知需要状态的对象来更新状态。iOS和UIKit在设计上是命令式的,如TableView的DataSource,委托模式强制将状态保存在委托中,以在请求发生时,为TableView提供数据. 使用ReactiveCocoa的原因数据的改变导致View的改变,现在我们常用的一般是命令式的编程,控制器去发动数据的改变,然后保存数据的状态,然后再去驱动View来加载最新的数据状态. 随着业务变得更加复杂,这种做法导致Controller上有一堆的状态数据,而这些状态数据错综复杂,互相影响,导致代码可读性较差,所以我们倾向于使用一种函数式编程的方式.数据修改后,直接设置View,而不是保存数据的状态,或者说将这步数据状态保存的操作抽离出来,不放在业务代码中,以提升代码的可读性,这就是使用 Introduction
事件流已经统一了Cocoa中的常用的异步和实现处理,如:
将这些事件处理统一成一种信号的形式,所以就可以声明链式处理和聚合信号。 总结,很多iOS程序,对于事件的处理和响应是基于应用状态的,随着回调的增加和状态变量的增加,处理这些事件的代码就回变得异常复杂,这就是我们使用ReactiveCocoa的原因,通过响应式的处理以消除中间状态,增加代码可读性和扩展性,降低复杂度。 调试地狱RAC通过一堆Block来实现事件流的传递,所以调试是意见很可怕的事情,在信号处理中的断点,会发现整个堆栈上全是RAC自己处理信号发送信号的操作,很难找到有用的信息,找到当前断点的上一级触发者。 RAC中推荐通过在事件流中加入副作用来进行调试。 ReactiveCocoa与 RxSwift.
主要区别在于RAC中有冷热信号的概念,这是RAC中的一个核心特点,后面会介绍到。RX则把冷热信号统一。 目前我们主要是在OC上开发,以后OC和Swift混编,这些情况下使用RAC都会更加方便一点。 Overview在RAC的世界中,使用信号流处理事件. 信号的发送者,称为 StreamsStream,由 值可以立刻被拿到,也可以在未来被拿到,但收到值是顺序的。不可能在流上不接受到第一个值而直接获取第二个值。 Stream是
Signals信号由 信号发送三种不同的事件:
next的事件可以有任意数量个,但是 Subscription
一个订阅的创建可以通过调用 Subjects
不在block中处理应用逻辑,而是将这些block发送给一个共享的Subject来处理。
Commands
这个属性通常与UI控件结合在一起,用 Connections
信号默认是冷的,即每当一个新的订阅者添加的时候,他们才开始处理事件,发送信号。这是一件可取的做法,数据会在每次订阅的时候刷新。但是对于有副作用的信号,或者操作消耗太多资源的信号(如网络请求),显然是有问题的。尤其是在RAC中,每次都信号进行的逻辑处理操作都是在订阅前一个信号。 通过RACSignal上的 Sequences
一般用于遍历数组 : NSArray *numbers = @[@(1),@(2),@(3),@(4),@(5)]; NSArray *result = [[[[numbers rac_sequence] filter:^BOOL(NSNumber *value) { return [value intValue] %2 ==0; }] map:^id(NSNumber *value) { long square = [value intValue] * [value intValue]; return @(square); }] array]; NSLog(@"results = %@",result); Disposables
Schedulers
Value typesRAC提供了一些类在Stream中传递值。
Basic Operators介绍几个简单的基础的操作符. Performing side effects with signalsSubscription使用 ACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; // Outputs: A B C D E F G H I [letters subscribeNext:^(NSString *x) { NSLog(@"%@",x); }]; 在冷信号中,每次订阅信号都会执行副作用. Injecting effects使用 __block unsigned subscriptions = 0; RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { subscriptions++; [subscriber sendCompleted]; return nil; }]; // Does not output anything yet loggingSignal = [loggingSignal doCompleted:^{ NSLog(@"about to complete subscription %u",subscriptions); }]; // Outputs: // about to complete subscription 1 // subscription 1 [loggingSignal subscribeCompleted:^{ NSLog(@"subscription %u",subscriptions); }];
Transforming streams这些操作,将一个信号流转变为一个新的信号流. Mapping使用 // 以" "为间隔符创建sequence RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; // Contains: AA BB CC DD EE FF GG HH II RACSequence *mapped = [letters map:^(NSString *value) { return [value stringByAppendingString:value]; }]; Filtering使用 RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; // Contains: 2 4 6 8 RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) { return (value.intValue % 2) == 0; }]; Combining streams将多个信号流聚合成一个信号流 Concatenating使用 RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9 RACSequence *concatenated = [letters concat:numbers]; Flattening使用 RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; RACSequence *sequenceOfSequences = @[ letters,numbers ].rac_sequence; // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9 RACSequence *flattened = [sequenceOfSequences flatten]; 这里 再聚一个聚合信号的例子: RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { [subscriber sendNext:letters]; [subscriber sendNext:numbers]; [subscriber sendCompleted]; return nil; }]; RACSignal *flattened = [signalOfSignals flatten]; // Outputs: A 1 B C 2 [flattened subscribeNext:^(NSString *x) { NSLog(@"%@",x); }]; [letters sendNext:@"A"]; [numbers sendNext:@"1"]; [letters sendNext:@"B"]; [letters sendNext:@"C"]; [numbers sendNext:@"2"]; Mapping and flattening使用
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; // Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 RACSequence *extended = [numbers flattenMap:^(NSString *num) { return @[ num,num ].rac_sequence; }]; // Contains: 1_ 3_ 5_ 7_ 9_ RACSequence *edited = [numbers flattenMap:^(NSString *num) { if (num.intValue % 2 == 0) { return [RACSequence empty]; } else { NSString *newNum = [num stringByAppendingString:@"_"]; return [RACSequence return:newNum]; } }];
RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; [[letters flattenMap:^(NSString *letter) { return [database saveEntriesForLetter:letter]; }] subscribeCompleted:^{ NSLog(@"All database entries saved successfully."); }]; Combining signals将多个信号聚合成一个信号. Sequencing使用 RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; // The new signal only contains: 1 2 3 4 5 6 7 8 9 // // But when subscribed to,it also outputs: A B C D E F G H I RACSignal *sequenced = [[letters doNext:^(NSString *letter) { NSLog(@"%@",letter); }] then:^{ return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal; }]; 使用场景,执行完前一个信号的所有副作用,然后开始另一个新信号,将其返回值作为真正的信号值 传递. Merging
RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *merged = [RACSignal merge:@[ letters,numbers ]]; // Outputs: A 1 B C 2 [merged subscribeNext:^(NSString *x) { NSLog(@"%@",x); }]; [letters sendNext:@"A"]; [numbers sendNext:@"1"]; [letters sendNext:@"B"]; [letters sendNext:@"C"]; [numbers sendNext:@"2"]; Combining lastest values
RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *combined = [RACSignal combineLatest:@[ letters,numbers ] // reduce的block的参数可以自行添加,但顺序要与combineLatest中信号的顺序相同. reduce:^(NSString *letter,NSString *number) { return [letter stringByAppendingString:number]; }]; // Outputs: B1 B2 C2 C3 [combined subscribeNext:^(id x) { NSLog(@"%@",x); }]; [letters sendNext:@"A"]; [letters sendNext:@"B"]; [numbers sendNext:@"1"]; [numbers sendNext:@"2"]; [letters sendNext:@"C"]; [numbers sendNext:@"3"]; 一定要注意这里,聚合的所有信号都有第一个值后,这个聚合信号才会发送第一个值,所以这里 然后就要与另外一个方法 zip stream压缩信号.与上面的 举例,还是类似 RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *combined = [RACSignal zip:@[ letters,numbers ] reduce:^(NSString *letter,NSString *number) { return [letter stringByAppendingString:number]; }]; // Outputs: A1 B2 C3 [combined subscribeNext:^(id x) { NSLog(@"%@",x); }]; [letters sendNext:@"A"]; [letters sendNext:@"B"]; [numbers sendNext:@"1"]; [numbers sendNext:@"2"]; [letters sendNext:@"C"]; [numbers sendNext:@"3"]; 在这里,最终输出地结果是 Switching使用 RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSubject *signalOfSignals = [RACSubject subject]; RACSignal *switched = [signalOfSignals switchToLatest]; // Outputs: A B 1 D [switched subscribeNext:^(NSString *x) { NSLog(@"%@",x); }]; [signalOfSignals sendNext:letters]; [letters sendNext:@"A"]; [letters sendNext:@"B"]; [signalOfSignals sendNext:numbers]; [letters sendNext:@"C"]; [numbers sendNext:@"1"]; [signalOfSignals sendNext:letters]; [numbers sendNext:@"2"]; [letters sendNext:@"D"]; 当信号中的信号发送 RACSubject *signalOfSignals = [RACSubject subject]; RACSignal *switched = [signalOfSignals switchToLatest]; [switched subscribeNext:^(id x) { NSLog(@"Next : %@",x); } error:^(NSError *error) { NSLog(@"Error : %@",error); } completed:^{ NSLog(@"Completed!"); }]; RACSubject *signalA = [RACSubject subject]; [signalOfSignals sendNext:signalA]; [signalA sendNext:@"A"]; [signalA sendCompleted]; [signalA sendNext:@"AA"]; RACSubject *signalB = [RACSubject subject]; [signalOfSignals sendNext:signalB]; [signalB sendNext:@"B"]; [signalB sendError:[NSError errorWithDomain:@"error" code:1 userInfo:nil]]; [signalB sendNext:@"BB"]; RACSubject *signalC = [RACSubject subject]; [signalOfSignals sendNext:signalC]; [signalC sendNext:@"C"]; 上面这段代码示例中,输出结果为 : Next : A Next : B Error : Error Domain=error Code=1 "(null)" 信号A发送完成后,AA的信号就无法发送给 Design Guidelines关于RACSequence的一些特性延迟加载在 NSArray *strings = @[ @"A",@"B",@"C" ]; RACSequence *sequence = [strings.rac_sequence map:^(NSString *str) { return [str stringByAppendingString:@"_"]; }]; 只有真正使用时,才会进行计算,获取到真正的值;如通过 而且,只会计算一次,即多次访问 如果不需要这种延迟加载,而需要在以来开始的时候初始化整个数组,那就使用 计算操作是同步执行的,这需要注意一下。如果数组的计算操作是比较耗费时间的,可以通过接口 副作用只执行一次对于 NSArray *strings = @[ @"A",@"C" ]; RACSequence *sequence = [strings.rac_sequence map:^(NSString *str) { NSLog(@"%@",str); return [str stringByAppendingString:@"_"]; }]; // Logs "A" during this call. 进行计算,执行block中方法,所以有log NSString *concatA = sequence.head; // Logs "B" during this call. NSString *concatB = sequence.tail.head; // Does not log anything. 已经完成计算,不会再输出log。 NSString *concatB2 = sequence.tail.head; 关于RACSignal一些需要注意的地方Signal events are serialized信号事件是连续的串行的。一个信号可以分发事件到任何一个线程,连续的事件可以被选择在不同的线程或者 RAC保证,不会有两个信号同时到达,所以不会有两个信号的事件同时在一个线程上被激活的可能。即在RAC中,当一个事件在处理过程中,不会有其他事件被分发。只有当事件被处理完成后才会有新的事件发送出去。 这就意味着 : 在 Subscription will always occur on a scheduler订阅操作,始终执行在一个 如果在订阅时,执行代码 再说明一下这个 Errors are propagated immediately在RAC中, 但这并表示 ,对于 Side effects occur for each subscription每一次对信号的订阅,都会触发副作用。原因很简单,因为这些信号是冷信号,冷信号会在订阅时执行。而需要注意的是,所有对信号的操作,都是订阅信号,并发送新的信号。 想要取消这种效果,那就是用热信号吧。 再次说明一下,冷信号的副作用效果,会产生许多问题,且难以发现,一定要注意。要理解冷热信号的区别。 Subscriptions are automatically disposed upon completion or error当一个消息发送了 而释放信号时,要对那些 文件操作或者网络操作等,进行资源释放和过程中断。 Best practicesUse descriptive declarations for methods and properties that return a signal当一个方法或者属性返回一个 对于声明一个信号,有以下三个关键性的问题:
热信号且没有副作用,这种情况应该将信号作为一种属性。使用属性,表明对信号的订阅不需要进行初始化,而且添加新的订阅也不会改变这个用法。信号的属性一般被命名为 冷信号且无副作用,这种情况应该作为一个函数,且命名使用一个名词来表示,如 有副作用的信号,信号应该是以方法形式返回,并表示动作,如 Indent stream operations consistently使用RAC书写代码时,在处理信号中得操作流很容易变得很重很多,大量的操作符与block聚集在一起,如果没有进行很好地格式化,那这段代码就将变得乱七八糟.所以,建议,在流的处理过程中,对操作符进行缩进 : RACStream *result = [[[RACStream zip:@[ firstStream,secondStream ] reduce:^(NSNumber *first,NSNumber *second) { return @(first.integerValue + second.integerValue); }] filter:^ BOOL (NSNumber *value) { return value.integerValue >= 0; }] map:^(NSNumber *value) { return @(value.integerValue + 1); }]; Use the same type for all the values of a stream在一个流中,使用一种类型来作为各个过程的信号值.虽然RAC中支持使用任何类型的值作为信号值来传递,但是在一个完整地流中,使用多种不同类型的值,会导致代码可读性降低,也会增加订阅者的负担,必须更加小心地去处理这个奇怪的信号. Avoid retaining streams for too long不要持有 例如 :一个 Process only as much of a stream as needed保持一个 我们可以使用 Deliver signal events onto a known scheduler可以将信号使用 但是,尽量少得去切换 Make the side effects of a signal explicit明确地说明一个信号有副作用. 我们应该避免信号的副作用,因为我们很难控制副作用的发生. 但这种场景还是需要的,所以RAC中提供了 Share the side effects of a signal by multicasting在热信号中,分享副作用.使用 Debug streams by giving them names每个 RACSignal *signal = [[[RACObserve(self,username) distinctUntilChanged] take:3] filter:^(NSString *newUsername) { return [newUsername isEqualToString:@"joshaber"]; }]; NSLog(@"%@",signal); 如上面打印出来的结果是 : 可以通过 Avoid explicit subscriptions and disposal避免明确地 订阅和释放操作.而使用以下几个方法:
尽量使用RAC提供的方法来操作信号来导出一个正确的符合效果的信号流,供订阅. Avoid using subjects when possible避免使用
如果要使用 Memory ManagementRAC中得内存管理是很复杂的,但是这样做的目的是,使用者不用持有信号来驱动信号发送的过程. 除了那些会长期存在的信号,会被以属性的形式持有,一般不要去持有信号.
Subscribers当使用 Finite or Shore-Lived Signals在RAC的内存关联中,一个重要的注意事项就是,订阅会在 completion 或是error时终止,订阅者也会被移除. 这样,信号的生命周期也就会跟随事件流的逻辑生命周期. Infinite Signals会有一些不会自行结束的信号存在,所以需要 信号订阅的 Signlas Derived from self有些信号是有self衍生出来的.如 建议使用 但很多时候,有一种更好地写法来解决循环指针的问题,如对于一般写法: @weakify(self); [RACObserve(self,username) subscribeNext:^(NSString *username) { @strongify(self); [self validateUsername]; }]; 实际上我们可以这样写: [self rac_liftSelector:@selector(validateUsername:) withSignals:RACObserve(self,username),nil]; 或者这样写 : RACSignal *validated = [RACObserve(self,username) map:^(NSString *username) { // Put validation logic here. return @YES; }]; 冷热信号详解这里的内容主要是在学习 美团的技术分享文章,里面几张图也是直接从这里拿过来的. 然后我们再来讨论这个RAC中一个重点问题.也就是冷热信号. 冷热信号的起源来自于RX的
产生热信号的原因,有些信号我们不想再订阅时就执行一次,而是全局共享一个信号.如网络请求的信号,我们并不希望在每次对网络请求结果信号进行订阅时,就执行一次新的网络请求.所以我们要将信号转换为热信号,以只执行一次网络访问,而结果可以供多次订阅共享. 热信号都属于一个类 RACSubject *subject = [RACSubject subject]; RACSubject *replaySubject = [RACReplaySubject subject]; [[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{ // Subscriber 1 [subject subscribeNext:^(id x) { NSLog(@"Subscriber 1 get a next value: %@ from subject",x); }]; [replaySubject subscribeNext:^(id x) { NSLog(@"Subscriber 1 get a next value: %@ from replay subject",x); }]; // Subscriber 2 [subject subscribeNext:^(id x) { NSLog(@"Subscriber 2 get a next value: %@ from subject",x); }]; [replaySubject subscribeNext:^(id x) { NSLog(@"Subscriber 2 get a next value: %@ from replay subject",x); }]; }]; [[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{ [subject sendNext:@"send package 1"]; [replaySubject sendNext:@"send package 1"]; }]; [[RACScheduler mainThreadScheduler] afterDelay:1.1 schedule:^{ // Subscriber 3 [subject subscribeNext:^(id x) { NSLog(@"Subscriber 3 get a next value: %@ from subject",x); }]; [replaySubject subscribeNext:^(id x) { NSLog(@"Subscriber 3 get a next value: %@ from replay subject",x); }]; // Subscriber 4 [subject subscribeNext:^(id x) { NSLog(@"Subscriber 4 get a next value: %@ from subject",x); }]; [replaySubject subscribeNext:^(id x) { NSLog(@"Subscriber 4 get a next value: %@ from replay subject",x); }]; }]; [[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{ [subject sendNext:@"send package 2"]; [replaySubject sendNext:@"send package 2"]; }]; 输出的结果是 : 2016-06-30 23:34:25.722 TestPods[17259:2788197] Subscriber 1 get a next value: send package 1 from subject 2016-06-30 23:34:25.723 TestPods[17259:2788197] Subscriber 2 get a next value: send package 1 from subject 2016-06-30 23:34:25.723 TestPods[17259:2788197] Subscriber 1 get a next value: send package 1 from replay subject 2016-06-30 23:34:25.724 TestPods[17259:2788197] Subscriber 2 get a next value: send package 1 from replay subject 2016-06-30 23:34:25.834 TestPods[17259:2788197] Subscriber 3 get a next value: send package 1 from replay subject 2016-06-30 23:34:25.834 TestPods[17259:2788197] Subscriber 4 get a next value: send package 1 from replay subject 2016-06-30 23:34:26.818 TestPods[17259:2788197] Subscriber 1 get a next value: send package 2 from subject 2016-06-30 23:34:26.818 TestPods[17259:2788197] Subscriber 2 get a next value: send package 2 from subject 2016-06-30 23:34:26.818 TestPods[17259:2788197] Subscriber 3 get a next value: send package 2 from subject 2016-06-30 23:34:26.819 TestPods[17259:2788197] Subscriber 4 get a next value: send package 2 from subject 2016-06-30 23:34:26.819 TestPods[17259:2788197] Subscriber 1 get a next value: send package 2 from replay subject 2016-06-30 23:34:26.819 TestPods[17259:2788197] Subscriber 2 get a next value: send package 2 from replay subject 2016-06-30 23:34:26.820 TestPods[17259:2788197] Subscriber 3 get a next value: send package 2 from replay subject 2016-06-30 23:34:26.820 TestPods[17259:2788197] Subscriber 4 get a next value: send package 2 from replay subject 根据时间线画图如下: 而如果是冷信号的情况的话,就有如下时间线: 可以发现,对于冷信号,类似于重播,每个订阅者都会观察到整个消息的处理过程.而对于 而还有一个 发现这个信号会保存之前发送过的信号,在新的对象订阅时,将之前的信号发送. 将一个冷信号变成热信号
观察以下代码: RACSignal *coldSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"Cold signal be subscribed."); [[RACScheduler mainThreadScheduler] afterDelay:1.5 schedule:^{ [subscriber sendNext:@"A"]; }]; [[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{ [subscriber sendNext:@"B"]; }]; [[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{ [subscriber sendCompleted]; }]; return nil; }]; RACSubject *subject = [RACSubject subject]; NSLog(@"Subject created."); [[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{ [coldSignal subscribe:subject]; }]; [subject subscribeNext:^(id x) { NSLog(@"Subscriber 1 recieve value:%@.",x); }]; [[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{ [subject subscribeNext:^(id x) { NSLog(@"Subscriber 2 recieve value:%@.",x); }]; }]; 输出结果为 : 2016-07-01 16:32:04.829 TestPods[17618:2842318] Subject created. 2016-07-01 16:32:07.029 TestPods[17618:2842318] Cold signal be subscribed. 2016-07-01 16:32:08.669 TestPods[17618:2842318] Subscriber 1 recieve value:A. 2016-07-01 16:32:10.319 TestPods[17618:2842318] Subscriber 1 recieve value:B. 2016-07-01 16:32:10.319 TestPods[17618:2842318] Subscriber 2 recieve value:B. 得到下图: 这样自行处理热信号,过于简单,有一些问题,如当 //创建一个普通的热信号 -(RACMulticastConnection *)publish; // 创建一个热信号,并将值发送给一个RACSubject对象 -(RACMulticastConnection *)multicast:(RACSubject *)subject; // 创建重播热信号,并立即订阅,信号使用RACReplaySubject,即会重播已经发送的所有信号 -(RACSignal *)replay; // 创建一个热信号,使用RACReplaySubject,但设置capacity为1,即只会重播一次信号 -(RACSignal *)replayLast; // 创建一个热信号.但不立即订阅,等待其他人订阅这个热信号. -(RACSignal *)replayLazily; RAC提供的热信号处理的具体实现RAC提供以上五个方法中,最重要的就是 /// implementation RACSignal (Operations) -(RACMulticastConnection *)multicast:(RACSubject *)subject { [subject setNameWithFormat:@"[%@] -multicast: %@",self.name,subject.name]; RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject]; return connection; } /// implementation RACMulticastConnection -(id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject { NSCParameterAssert(source != nil); NSCParameterAssert(subject != nil); self = [super init]; if (self == nil) return nil; _sourceSignal = source; _serialDisposable = [[RACSerialDisposable alloc] init]; _signal = subject; return self; } #pragma mark Connecting -(RACDisposable *)connect { BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0,1,&_hasConnected); if (shouldConnect) { self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal]; } return self.serialDisposable; } -(RACSignal *)autoconnect { __block volatile int32_t subscriberCount = 0; return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { OSAtomicIncrement32Barrier(&subscriberCount); RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber]; RACDisposable *connectionDisposable = [self connect]; return [RACDisposable disposableWithBlock:^{ [subscriptionDisposable dispose]; if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) { [connectionDisposable dispose]; } }]; }] setNameWithFormat:@"[%@] -autoconnect",self.signal.name]; } 简单说明一下流程:
然后再来看一下 另外4个方法的实现: /// implementation RACSignal (Operations) -(RACMulticastConnection *)publish { RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish",self.name]; RACMulticastConnection *connection = [self multicast:subject]; return connection; } -(RACSignal *)replay { RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay",self.name]; RACMulticastConnection *connection = [self multicast:subject]; [connection connect]; return connection.signal; } -(RACSignal *)replayLast { RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast",self.name]; RACMulticastConnection *connection = [self multicast:subject]; [connection connect]; return connection.signal; } -(RACSignal *)replayLazily { RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]]; return [[RACSignal defer:^{ [connection connect]; return connection.signal; }] setNameWithFormat:@"[%@] -replayLazily",self.name]; }
RACCommand
我们首先来看一下, executionSignals在调用 RACCommand *comd = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:[NSString stringWithFormat:@"INPUT :%@",input]]; [subscriber sendError:[NSError errorWithDomain:@"ddd" code:1 userInfo:nil]]; return nil; }]; }]; [[comd.executionSignals switchToLatest] subscribeNext:^(id x) { NSLog(@"Next : %@",x); } error:^(NSError *error) { NSLog(@"Next : %@",error); } completed:^{ NSLog(@"Completed"); }]; [comd.executionSignals subscribeNext:^(id x) { NSLog(@"Signal %@ ",x); }]; [comd.errors subscribeNext:^(id x) { NSLog(@"error 里才有? %@",x); }]; [comd execute:@"hello world"]; 如上代码,最终输出为: 2016-07-02 20:39:13.068 TestPods[23173:3420416] Signal <RACDynamicSignal: 0x7ff2d9406d30> name: 2016-07-02 20:39:13.068 TestPods[23173:3420416] Next : INPUT :hello world 2016-07-02 20:39:13.069 TestPods[23173:3420416] Completed 2016-07-02 20:39:13.069 TestPods[23173:3420416] error 里才有? Error Domain=ddd Code=1 "(null)" 但是,可以通过
executing表示当前命令是否正在执行的信号.
上面说到,当 enabled决定 只在两种情况下返回 NO :
除了这两种情况,一般都返回YES. 这个信号 一般用于操作UI控件的状态,如 errors
注意,这里错误订阅需要订阅 allowsConcurrentExecution
默认是 - (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;以一个返回 - (id)initWithEnabled:(RACSignal )enabledSignal signalBlock:(RACSignal (^)(id input))signalBlock;初始化时,设置 对于 对于 - (RACSignal *)execute:(id)input;当
这个函数是有返回值,返回值也是一个 总结 RACCommand在 我们将 我们可以将很多任务通过 RACCommand *logginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id params) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:BASEURL] sessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; [manager POST:LOGINURL parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task,id _Nullable responSEObject) { [subscriber sendNext:responSEObject]; [subscriber sendCompleted]; } failure:^(NSURLSessionDataTask * _Nullable task,NSError * _Nonnull error) { [subscriber sendError:error]; }]; return nil; }]; }]; 像这样通过 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |