RAC 被描述为函数响应式编程。
编程思想
函数式编程 :使用高阶函数,例如函数用其他函数作为参数。 响应式编程:关注于数据流和变化传播。 链式编程 : 是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3),注意点:要想达到链式编程方法的返回值必须是一个( (返回值是本身对象的)block)
入门 入门第一部分 Signal 传递的 data Signal 传递的 data 是 event,它所传递的 event 包括 3 种:值事件、完成事件和错误事件。其中在传递值事件时,可以携带数据.传递值事件/完成事件/错误事件的本质就是向 subscriber 发送sendNext:、sendComplete以及sendError:消息
Signal 在其生命周期内,可以传递任意多个值事件,但最多只能传递一个完成事件或错误事件;换句话说,一旦 Signal 的事件流中出现了错误事件或者完成事件,之后产生的任何事件都是无效的.
Signal的简单使用 创建信号、订阅信号、订阅过程
创建获取信号
创建单元信号
创建动态信号
通过 Cocoa 桥接
从别的信号变换而来
由序列变换而来
Cocoa桥接
1 2 3 4 RACSignal *signal6 = [objectrac_signalForSelector:@selector (setFrame:)]; RACSignal *signal7 = [control rac_signalForControlEvents:UIControlEventTouchUpInside ]; RACSignal *signal8 = [object rac_willDeallocSignal]; RACSignal *signal9 = RACObserve(object, keyPath);
信号变换
1 2 3 RACSignal *signal10 = [signal1 map:^id (id value) { return someObject; }];
序列变换
1 RACSignal *signal11 = sequence.signal;
订阅信号 订阅信号的方式有 3 种:
通过subscribeNext:error:completed:方法订阅
RAC 宏绑定
Cocoa 桥接
通过subscribeNext:error:completed:方法订阅
1 2 3 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void ));
RAC 宏绑定
1 2 RAC(view, backgroundColor) = signal10;
Cocoa 桥接
1 2 3 [object rac_liftSelector:@selector (someSelector:) withSignals:signal1, signal2, nil ]; [object rac_liftSelector:@selector (someSelector:) withSignalsFromArray:@[signal1, signal2]]; [object rac_liftSelector:@selector (someSelector:) withSignalOfArguments:signal1];
订阅过程 订阅过程指的是信号被订阅的处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) { [subscriber sendNext:@"1" ]; [subscriber sendNext:@"2" ]; [subscriber sendCompleted]; [subscriber sendNext:@"3" ]; return [RACDisposable disposableWithBlock:^{ NSLog (@"dispose" ); }]; }]; [signal subscribeNext:^(id x) { NSLog (@"next value is : %@" , x); } error:^(NSError *error) { NSLog (@"error : %@" , error); } completed:^{ NSLog (@"completed" ); }];
注意:
RACSignal的每一个操作都会返回一个RACSignal。
RACSequence是RAC中的集合类,可以实现OC对象与信号中传递值之间的转换,RAC类库中提供了NSArray,NSDictionary等集合类的分类供其转换
RAC中信号处理的常用方法
Map实现类型转换
信号中的信号 flattenMap
添加附加的操作 doNext
Reduce 聚合: 将多个信号发出的值进行聚合
ReactiveCocoa操作方法之秩序(控制流操作)。
doNext:执行Next之前,会先执行这个Block
doCompleted: 执行sendCompleted之前,会先执行这个Block
1 2 3 4 5 6 7 8 9 [RACObserve(self , selectedRows) subscribeNext:^(NSSet *currentlySelected) { NSLog (@"Currently selected: %@" , currentlySelected); }]; self .selectedRows = [NSMutableSet set];- (void )tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [self .selectedRows addObject:indexPath]; }
上面RACObserve回调只会调用一次,初始化的时候,为什么addObject的时候没有出发信号? 因为addObject没有触发KVO事件。解决通过协议触发KVO
1 2 3 4 - (void )tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [[self mutableSetValueForKey:@"selectedRows" ] addObject:indexPath]; }
入门第二部分
进阶 RACSignal的subscription过程 RACSignal的常见用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -(RACSignal *)signInSignal { return [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) { [self .signInService signInWithUsername:self .usernameTextField.text password:self .passwordTextField.text complete:^(BOOL success) { [subscriber sendNext:@(success)]; [subscriber sendCompleted]; }]; return nil ; }];3 } [[self signInSignal] subscribeNext:^(id x) { NSLog (@"Sign in result: %@" , x); }];
Subscription过程概括 RACSignal的Subscription过程概括起来可以分为三个步骤:
[RACSignal createSignal]来获得signal
[signal subscribeNext:]来获得subscriber,然后进行subscription
进入didSubscribe,通过[subscriber sendNext:]来执行next block
步骤一:[RACSignal createSignal]来获得signal
1 2 3 4 5 6 7 8 9 10 RACSignal.m中: + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { return [ RACDynamicSignal createSignal :didSubscribe]; } RACDynamicSignal.m中 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { RACDynamicSignal *signal = [[ self alloc ] init ]; signal-> _didSubscribe = [didSubscribe copy ]; return [signal setNameWithFormat : @"+createSignal:" ]; }
[RACSignal createSignal]会调用子类RACDynamicSignal的createSignal来返回一个signal,并在signal中保存后面的 didSubscribe这个block
步骤二:[signal subscribeNext:]来获得subscriber,然后进行subscription
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 RACSignal.m中: - ( RACDisposable *)subscribeNext:( void (^)( id x))nextBlock { RACSubscriber *o = [ RACSubscriber subscriberWithNext :nextBlock error : NULL completed : NULL ]; return [ self subscribe :o]; } RACSubscriber.m中: + ( 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; } RACDynamicSignal.m中: - (RACDisposable *)subscribe:(id <RACSubscriber>)subscriber { 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; }
[signal subscribeNext]先会获得一个subscriber,这个subscriber中保存了nextBlock、errorBlock、completedBlock
由于这个signal其实是RACDynamicSignal类型的,这个[self subscribe]方法会调用步骤一中保存的didSubscribe,参数就是1中的subscriber
步骤三:进入didSubscribe,通过[subscriber sendNext:]来执行next block
1 2 3 4 5 6 7 8 RACSubscriber.m中: - (void )sendNext:(id )value { @synchronized (self ) { void (^nextBlock)(id ) = [self .next copy ]; if (nextBlock == nil ) return ; nextBlock(value); } }
任何时候这个[subscriber sendNext:],就直接调用nextBlock
multicast和replay 有关的RACSignal的操作 在RACSignal+Operation.h中,定义了5个跟我们这个主题有关的RACSignal的操作
1 2 3 4 5 6 7 8 9 10 RACSignal+Operation.h中 - (RACMulticastConnection *)publish; - (RACMulticastConnection *)multicast:(RACSubject *)subject; - (RACSignal *)replay; - (RACSignal *)replayLast; - (RACSignal *)replayLazily;
这几个操作的区别很细微,但用错的话很容易出问题。只有理解了原理之后,才明白它们之间的细微区别,很多时候我们意识不到需要用这些操作,这就可能因为side effects执行多次而导致程序bug
multicast && replay的应用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 RACSignal *networkRequest = [RACSignal createSignal:^(id <RACSubscriber> subscriber) { AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { [subscriber sendError:error]; }]; [client enqueueHTTPRequestOperation:operation]; return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }]; RACMulticastConnection *connection = [networkRequest multicast:[RACReplaySubject subject]]; [connection connect]; [connection.signal subscribeNext:^(id response) { NSLog (@"subscriber one: %@" , response); }]; [connection.signal subscribeNext:^(id response) { NSLog (@"subscriber two: %@" , response); }];
在上面的例子中,如果我们不用RACMulticastConnection的话,那就会因为执行了两次subscription而导致发了两次网络请求。
从上面的例子中,我们可以看到对一个Signal进行multicast之后,我们是对connection.signal进行subscription而不是原来的networkRequest。这点是”side effects should only occur once”的关键,我们将在后面解释
冷信号和热信号 冷热信号的概念源于.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable,两者的区别是:
Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。
Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。
冷信号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 -(void )racColdSingle { RACSignal* coldSingle=[RACSignal createSignal:^RACDisposable * _Nullable(id <RACSubscriber> _Nonnull subscriber) { [subscriber sendNext:@1 ]; [subscriber sendNext:@2 ]; [subscriber sendNext:@3 ]; [subscriber sendCompleted]; return nil ; }]; NSLog (@"Signal was created." ); [[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{ [coldSingle subscribeNext:^(id x) { NSLog (@"Subscriber 1 recveive: %@" , x); }]; }]; [[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{ [coldSingle subscribeNext:^(id x) { NSLog (@"Subscriber 2 recveive: %@" , x); }]; }]; }
1 2 3 4 5 6 7 8 2019-09-23 14:14:26.853285+0800 RACDemo[16585:117906] Signal was created. 2019-09-23 14:14:26.956126+0800 RACDemo[16585:117906] Subscriber 1 recveive: 1 2019-09-23 14:14:26.956339+0800 RACDemo[16585:117906] Subscriber 1 recveive: 2 2019-09-23 14:14:26.956559+0800 RACDemo[16585:117906] Subscriber 1 recveive: 3 2019-09-23 14:14:27.888112+0800 RACDemo[16585:117906] Subscriber 2 recveive: 1 2019-09-23 14:14:27.888267+0800 RACDemo[16585:117906] Subscriber 2 recveive: 2 2019-09-23 14:14:27.888363+0800 RACDemo[16585:117906] Subscriber 2 recveive: 3
信号在14:14:26.853时被创建,14:14:26.956依次接到1、2、3三个值,而在14:14:27.888再依次接到1、2、3三个值。说明了变量名为coldSingle的这个信号,在两个不同时间段的订阅过程中,分别完整地发送了所有的消息。
热信号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 -(void )racHoltSingal { RACMulticastConnection *connection = [[RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) { [[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{ [subscriber sendNext:@1 ]; }]; [[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{ [subscriber sendNext:@2 ]; }]; [[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{ [subscriber sendNext:@3 ]; }]; [[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{ [subscriber sendCompleted]; }]; return nil ; }] publish]; [connection connect]; RACSignal *signal = connection.signal; NSLog (@"Signal was created." ); [[RACScheduler mainThreadScheduler] afterDelay:1.1 schedule:^{ [signal subscribeNext:^(id x) { NSLog (@"Subscriber 1 recveive: %@" , x); }]; }]; [[RACScheduler mainThreadScheduler] afterDelay:2.1 schedule:^{ [signal subscribeNext:^(id x) { NSLog (@"Subscriber 2 recveive: %@" , x); }]; }]; }
来一一分析:
创建了一个信号,在1秒、2秒、3秒分别发送1、2、3这三个值,4秒发送结束信号。
对这个信号调用publish方法得到一个RACMulticastConnection。
让connection进行连接操作。
获得connection的信号。
分别在1.1秒和2.1秒订阅获得的信号。
1 2 3 4 2019-09-23 14:20:26.214718+0800 RACDemo[16872:122429] Signal was created. 2019-09-23 14:20:28.325190+0800 RACDemo[16872:122429] Subscriber 1 recveive: 2 2019-09-23 14:20:29.274356+0800 RACDemo[16872:122429] Subscriber 1 recveive: 3 2019-09-23 14:20:29.274537+0800 RACDemo[16872:122429] Subscriber 2 recveive: 3
[RACSignal publish]、- [RACMulticastConnection connect]、- [RACMulticastConnection signal]这几个操作生成了一个热信号
信号在14:20:26.214被创建14:20:28.325时订阅者1才收到2这个值,说明1这个值没有接收到,时间间隔是2秒多14:20:29.274时订阅者1和订阅者2同时收到3这个值,时间间隔是3秒多
热信号的如下特点:
热信号是主动的,即使你没有订阅事件,它仍然会时刻推送。如第二个例子,信号在50秒被创建,51秒的时候1这个值就推送出来了,但是当时还没有订阅者。而冷信号是被动的,只有当你订阅的时候,它才会发送消息。如第一个例子。
热信号可以有多个订阅者,是一对多,信号可以与订阅者共享信息。如第二个例子,订阅者1和订阅者2是共享的,他们都能在同一时间接收到3这个值。而冷信号只能一对一,当有不同的订阅者,消息会从新完整发送。如第一个例子,我们可以观察到两个订阅者没有联系,都是基于各自的订阅时间开始接收消息的。
RAC中的热信号 在RAC中,所有的热信号都属于一个类RACSubject
A subject, represented by the RACSubject class, is a signal that can be manually controlled.
Subjects can be thought of as the “mutable” variant of a signal, much like NSMutableArray is for NSArray. They are extremely useful for bridging non-RAC code into the world of signals.
For example, instead of handling application logic in block callbacks, the blocks can simply send events to a shared subject instead. The subject can then be returned as a RACSignal, hiding the implementation detail of the callbacks.
Some subjects offer additional behaviors as well. In particular, RACReplaySubject can be used to buffer events for future subscribers, like when a network request finishes before anything is ready to handle the result.
Subject具备如下三个特点:
Subject是可变的
Subject是非RAC到RAC的一个桥梁
Subject可以附加行为,例如RACReplySubject具备为未来订阅者缓冲事件的能力。
RACSubject几其子类是热信号
RACSingle排除RACSubject类以外的是冷信号
冷信号转化成热信号—广播 冷信号与热信号的本质区别在于是否保持状态,冷信号的多次订阅是不保持状态的,而热信号的多次订阅可以保持状态,所以一种将冷信号转换为热信号的方法就是,将冷信号订阅,订阅到的每一个时间通过RACSubject
发送出去,其它订阅者只订阅这个RACSubject
文章参考
ReactiveCocoa学习之路
Reactivecocoa(RAC)知其所以然
美团ReactiveCocoa