ReactiveCocoa终结篇
前面四篇文章我们分析了RAC的常用的基本用法和源码实现。今天,是关于RAC源码分析的最后一篇文章。今天介绍的两个类是RACMulticastConnection和RACCommand,这两个使用在现实开发中有着至关重要的作用,尤其是网络请求案例。
RACMulticastConnection
使用方法:
//1.创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"热门模块的数据"];
return nil;
}];
//2.把信号转换为连接类
// RACMulticastConnection *connection = [signal publish];
RACMulticastConnection *connection = [signal multicast:[RACReplaySubject subject]];
[connection.signal subscribeNext:^(id x) {
NSLog(@"订阅者1:%@",x);
}];
[connection.signal subscribeNext:^(id x) {
NSLog(@"订阅者2:%@",x);
}];
[connection connect];
对于这个RACMulticastConnection是做什么的,我们看来自官方的定义。
A multicast connection encapsulates the idea of sharing one subscription to a signal to many subscribers. This is most often needed if the subscription to the underlying signal involves side-effects or shouldn’t be called more than once.(多播连接封装了将一个订阅信号共享给许多订阅者的想法。如果对基础信号的订阅涉及副作用或不应被多次调用,则最常需要此方法。)
我们可以看出来跟名称相同MulticastConnection实现了多播连接。
思考:为什么设计一个多播连接,难道之前的信号不好吗?
个人根据官方定义认为,如果有一种需求,当一个信号需要被多个订阅者订阅时候,需要多次被调用,这样对于资源有一定的消耗,因此,采用多播连接调用,可以省去额外的资源开销从而使得效率更高。
了解完概念后,我们来看源码是如何实现的。
RACMulticastConnection 创建连接
通过一个信号来调用,从而使得这个信号可以被多个订阅者调用。
- (RACMulticastConnection *)multicast:(RACSubject *)subject {
[subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
return connection;
}
- (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;
}
核心代码,做了三个赋值操作,分别是对原信号的赋值,回收机制的赋值,信号的赋值。
- _sourceSignal: RACMulticastConnection自身就是一个信号,一个源信号;
- _signal:表示接受一个订阅者信号
当然,你可以使用下面这种方式来创建多播连接,
RACMulticastConnection *connection = [signal publish];
- (RACMulticastConnection *)publish {
RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
RACMulticastConnection *connection = [self multicast:subject];
return connection;
}
底层还是调用上面的方法进行创建多播,不过是做了二次封装而已。
看到这里,大家一定有一个疑问,为什么要传入一个订阅者呢???
RACMulticastConnection 转为信号订阅信号
这里,将connection转换成为信号,在通过信号的RACSignal提供的方法进行订阅信号
[connection.signal subscribeNext:^(id x) {
NSLog(@"订阅者1:%@",x);
}];
[connection.signal subscribeNext:^(id x) {
NSLog(@"订阅者2:%@",x);
}];
RACMulticastConnection 连接
使用时候,一定不要忘记连接着一个操作。
[connection connect];
- (RACDisposable *)connect {
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect) {
/* 订阅 */
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
这里面我们需要分析一下,首先,判断是否可连接,通过OSAtomicCompareAndSwap32Barrier来比较和交换32位二进制数据,如果之前的hasConnected是没有连接的,也就是说0和&_hasConnected是相同值时候,我们交换1给_hasConnected,让其变成可连接。
当交换完成变成可连接时候,我们开始了订阅信号操作,订阅的信号就解决了我们之前的疑惑,之所以传入一个订阅者,是为了在连接时候进行更好的订阅信号。
自己充当源信号订阅信号。
我们之前分析过,RACReplaySubject可以用数组存储需要订阅的信号,因此这里面订阅了subject当连接成功时候,会被一次性全部调用,并且通过subscribeNext显示输出。
还有一个主角RACCommand
RACCommand
RACCommand看起来更加憨态可掬,因为,它创建的时候,内部是含有信号的,也就是内部信号。
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"请求数据"];
[subscriber sendCompleted];
return nil;
}];
}];
self.command = command;
[command.executionSignals subscribeNext:^(id x) {
[x subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}];
[self.command execute:@1];
如果你看它的创建方法,你一定会吓疯的,这么长,我该从何入手。下面我们分解这个源码。
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock {
return [self initWithEnabled:nil signalBlock:signalBlock];
}
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock {
NSCParameterAssert(signalBlock != nil);
self = [super init];
if (self == nil) return nil;
/* 这里使用一个记录活跃可执行的信号 */
_activeExecutionSignals = [[NSMutableArray alloc] init];
_signalBlock = [signalBlock copy];
// A signal of additions to `activeExecutionSignals`.
RACSignal *newActiveExecutionSignals = [[[[[self
rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil]
reduceEach:^(id _, NSDictionary *change) {
NSArray *signals = change[NSKeyValueChangeNewKey];
if (signals == nil) return [RACSignal empty];
return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler];
}]
concat]
publish]
autoconnect];
/* 将newActiveExecutionSignals转换成为可执行信号 */
_executionSignals = [[[newActiveExecutionSignals
map:^(RACSignal *signal) {
return [signal catchTo:[RACSignal empty]];
}]
deliverOn:RACScheduler.mainThreadScheduler]
setNameWithFormat:@"%@ -executionSignals", self];
// `errors` needs to be multicasted so that it picks up all
// `activeExecutionSignals` that are added.
//
// In other words, if someone subscribes to `errors` _after_ an execution
// has started, it should still receive any error from that execution.
/* 调用多播用法 */
RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals
flattenMap:^(RACSignal *signal) {
return [[signal
ignoreValues]
catch:^(NSError *error) {
return [RACSignal return:error];
}];
}]
deliverOn:RACScheduler.mainThreadScheduler]
publish];
_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
[errorsConnection connect];
RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) {
return @(activeSignals.count > 0);
}];
_executing = [[[[[immediateExecuting
deliverOn:RACScheduler.mainThreadScheduler]
// This is useful before the first value arrives on the main thread.
startWith:@NO]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -executing", self];
RACSignal *moreExecutionsAllowed = [RACSignal
if:RACObserve(self, allowsConcurrentExecution)
then:[RACSignal return:@YES]
else:[immediateExecuting not]];
if (enabledSignal == nil) {
enabledSignal = [RACSignal return:@YES];
} else {
enabledSignal = [[[enabledSignal
startWith:@YES]
takeUntil:self.rac_willDeallocSignal]
replayLast];
}
_immediateEnabled = [[RACSignal
combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
and];
_enabled = [[[[[self.immediateEnabled
take:1]
concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -enabled", self];
return self;
}
第一板块:
_activeExecutionSignals = [[NSMutableArray alloc] init];
_signalBlock = [signalBlock copy];
这里面创建了一个活动的信号,和一个信号的block回调,我们斗胆猜测,这里后面可能会判断RACCommand中的内部信号是否是有效活跃的。
第二板块:
// A signal of additions to `activeExecutionSignals`.
RACSignal *newActiveExecutionSignals = [[[[[self
rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil]
reduceEach:^(id _, NSDictionary *change) {
NSArray *signals = change[NSKeyValueChangeNewKey];
if (signals == nil) return [RACSignal empty];
return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler];
}]
concat]
publish]
autoconnect];
我们通过之前创建的活跃信号来创建一个RACSignal的信号,并且转换成为可执行信号。
第三板块:
_executionSignals = [[[newActiveExecutionSignals
map:^(RACSignal *signal) {
return [signal catchTo:[RACSignal empty]];
}]
deliverOn:RACScheduler.mainThreadScheduler]
setNameWithFormat:@"%@ -executionSignals", self];
第四板块:
RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals
flattenMap:^(RACSignal *signal) {
return [[signal
ignoreValues]
catch:^(NSError *error) {
return [RACSignal return:error];
}];
}]
deliverOn:RACScheduler.mainThreadScheduler]
publish];
_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
[errorsConnection connect];
使用多播连接创建错误连接,并订阅。
看到这里,大致猜测一下它初始化时候做了保存传入的内部信号,并且检测其活跃性和可执行性的同时发送这个内部信号,如果有错误则利用多播连接进行发送,到此为止,我们知道这么多就够了。
[command.executionSignals subscribeNext:^(id x) {
[x subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}];
[self.command execute:@1];
我们继续将RACCommand转换成为一个可执行信号,然后订阅RACCommand信号,此时的x是内部信号,我们要得到内部信号发送的信号,就要在使用一个订阅信号,进行订阅其内部的信号,这是多么神奇的设计模式。
当[self.command execute:@1];时候,command变成可执行,就会收到订阅的消息。
同时注意,为了保证command持久有效,我们需要使用strong类型强引用command,防止被回收后无法成为可执行信号的悲惨下场。
至此,我们分析了关于ReactiveCocoa一些基础用法和源码分析,思路是最重要的,不一定需要看懂每一句源码,但是大致了解内部是如何设计和为什么这样设计才是最重要的思想。
对于RAC的高级用法,小编暂时还没有进行深入的研读,如果,对与宏定义有所理解,会专门开辟一个专题分析RAC宏的高级用法。
源码下载
中文解析源码: GitHub.