ReativeCocoaRACCommand
Posted 王飞飞不会飞
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReativeCocoaRACCommand相关的知识,希望对你有一定的参考价值。
-
使用
初始化一个commed需要一个返回类型为RACSignal类型的Block。定义好RACCommand之后可以调用execute:方法执行该命令
RACCommand *commend = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input)
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
NSLog(@"subscriber blick");
return [RACDisposable disposableWithBlock:^
NSLog(@"disposable Block");
];
];
];
[commend execute:@"111"];
执行完成可以发现。commend execute其实是执行了signal中的Block,从之前的文章可以得知。是该信号被订阅了。那么这中间到底发生了什么呢,带着问题,我们来看看源码。
-
源码解析
先看初始化的源码
- (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];
_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;
这里代码有的长,我们先看主要的。传入的Block其实只是做了个赋值。初始化了_executionSignals、_errors、_executing、_immediateEnabled、_enabled这些东西。
再看看execute的源码
- (RACSignal *)execute:(id)input
// `immediateEnabled` is guaranteed to send a value upon subscription, so
// -first is acceptable here.
BOOL enabled = [[self.immediateEnabled first] boolValue];
if (!enabled)
NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@
NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),
RACUnderlyingCommandErrorKey: self
];
return [RACSignal error:error];
RACSignal *signal = self.signalBlock(input);
NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);
// We subscribe to the signal on the main thread so that it occurs _after_
// -addActiveExecutionSignal: completes below.
//
// This means that `executing` and `enabled` will send updated values before
// the signal actually starts performing work.
RACMulticastConnection *connection = [[signal
subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]];
@weakify(self);
[self addActiveExecutionSignal:connection.signal];
[connection.signal subscribeError:^(NSError *error)
@strongify(self);
[self removeActiveExecutionSignal:connection.signal];
completed:^
@strongify(self);
[self removeActiveExecutionSignal:connection.signal];
];
[connection connect];
return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, [input rac_description]];
这里拿到了初始化中的signalBlock,并用这个block初始化了RACMulticastConnection,之后又调用了RACMulticastConnection 的connect方法。
- (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;
把Commend初始化传进来的Signal赋值给RACMulticastConnection的_signal。这些都是前期准备。再看看connect的源码
- (RACDisposable *)connect
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect)
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
return self.serialDisposable;
这时候执行了signal的subscribe方法,这个方法内部就会执行signal初始化的block了。那么整体走下来的效果其实就是。调用Commend的execute方法,就会执行Commend方法初始化传入SignalBlcok返回的Signal的初始化Block了。
-
纠正
以上的源码分析其实有个没注意的地方在RACCommend的execute方法中
RACSignal *signal = self.signalBlock(input);
NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);
// We subscribe to the signal on the main thread so that it occurs _after_
// -addActiveExecutionSignal: completes below.
//
// This means that `executing` and `enabled` will send updated values before
// the signal actually starts performing work.
RACMulticastConnection *connection = [[signal
subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]];
看最后一句其实还有一步使用siganl 又去处事化了一个RACSignal。看看这个方法的源码
- (RACSignal *)subscribeOn:(RACScheduler *)scheduler
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber)
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
RACDisposable *schedulingDisposable = [scheduler schedule:^
RACDisposable *subscriptionDisposable = [self subscribe:subscriber];
[disposable addDisposable:subscriptionDisposable];
];
[disposable addDisposable:schedulingDisposable];
return disposable;
] setNameWithFormat:@"[%@] -subscribeOn: %@", self.name, scheduler];
这里其实内部的这个RACSignal和外部的RACSignal(self)的关系是关联的。调用内部signal的subscribe方法,也会在Block中触发外部RACSignal的subscribe方法。所以理解为一个可能更好理解、没有那么多弯弯绕,所以在上面源码解析部分省略了。
-
图解
使用图解看看
-
问题
重复执行execute发现并不能继续触发Signal的block了。打断点可以发现
BOOL enabled = [[self.immediateEnabled first] boolValue];
不成立,导致后边的不能正常执行。
但如果将代码修改为。
- (void)commendTest
RACCommand *commend = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input)
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
NSLog(@"subscriber blick");
//新增加代码
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^
NSLog(@"disposable Block");
];
];
];
[commend execute:@"111"];
这样问题就迎刃而解,那么这其实的逻辑是如何的,继续来啃源码吧。
RACMulticastConnection *connection = [[signal
subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]];
看看这里回掉出来的subscribe的来历。经过阅读源码,发现是跟RACSubject相关的东西,该类继承自RACSignal,遵循 RACSubscriber协议。这个留在一下篇再讲解吧。
以上是关于ReativeCocoaRACCommand的主要内容,如果未能解决你的问题,请参考以下文章