在閱讀源碼之前容我拋出個小問題,看看下面的代碼??
//發送驗證碼
-(void)sendCodeRequesSignal{
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[self.accountServiceAssembly.authService sendResetPWDOTPCoder:self.phone withSuccessCallBack:^(id data) {
[subscriber sendNext:data];
[subscriber sendCompleted];
} withFailedCallBack:^(NSError *error) {
error = [HttpErrorCodeHandler errorWithCode:error.code userinfo:nil];
[subscriber sendError:error];
}];
return nil;
}];
[signal subscribeNext:^(id data) {
NSLog(@"%@", data);
}];
}
這是公司項目中對RAC的一段使用,生成的RACSignal實例自始至終都沒有被引用,而這里是有一個異步請求的。作為一個熱信號,請求結束后signal是應該已經釋放掉了,又是如何做出響應的???(聰明的你可能已經猜到了與subscriber有關??)。
好了,上面的問題先放一下。本文不會詳細的講述ReactiveCocoa各API的使用,著重說說一下幾點 :
1、RACSignal源碼
2、RACCommond源碼
3、bind方法
4、踩坑
<b>一、RACSignal</b>
下面是RACSignal最基本的一個使用??
RACSignal *signal = [RACDynamicSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//1、發送信號
[subscriber sendNext:@"窈窕淑女"];
// //理論上這里可以對整個信息通道做任意修改,直接屬性調用nextBlock發送信號
RACPassthroughSubscriber *passSubscriber = (RACPassthroughSubscriber *)subscriber;
RACSubscribeNextBlock nextBlock = [[passSubscriber valueForKeyPath:@"innerSubscriber"] valueForKeyPath:@"next"];
nextBlock(@"直接屬性調用");
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"清理工作");
}];
}];
//第一次訂閱信號
RACDisposable *disposeable1 = [signal subscribeNext:^(id x1) {
NSLog(@"君子好逑1");
}];
//第二次訂閱信號
RACDisposable *disposeable2 = [signal subscribeNext:^(id x2) {
NSLog(@"君子好逑2");
}];
//生成靜態(block不執行)信息->訂購(觸發信號)->訂購事件做出響應
這里初始化了一個信號,并做了兩次訂閱。先看下信號是怎么生成的, RACSignal有一個RACDynamicSignal的子類只有create和subscribe兩個接口
+ (RACSignal *)createSignal:(RACDidSubscribeBlock)didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
這里只生成了一個signal對象,并存儲了入參didSubscribe block,從字面量不難猜測是在訂閱之后執行的。
接著我們來看下subscribeNext:的實現
- (RACDisposable *)subscribeNext:(RACSubscribeNextBlock)nextBlock {
NSCParameterAssert(nextBlock != NULL);
//傳入nextBlock后生成一個記錄此任務的訂購者(RACSubscriber);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
//生成的"訂購者"又如何與信號(self)關聯上呢?-> RACDynamicSignal
return [self subscribe:o];
}
+ (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;
}
從訂閱者RACSubscriber的初始化api可知,一個訂閱者是可以包含next、error、complete。而且如下兩種實現方式并不是等效的,雖然效果可能是一樣的(后面會講到):
//訂閱方法1
[signal subscribeNext:^(id x) {
}];
[signal subscribeError:^(NSError *error) {
}];
[signal subscribeCompleted:^{
}];
//訂閱方法2
[signal subscribeNext:^(id x) {
} error:^(NSError *error) {
} completed:^{
}];
這里我們已經生成了一個完整的訂購者和信號,但兩者又是如何關聯起來的呢,繼續看最后一句的實現,RACDynamicSignal中最重要的一個接口
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
//相當于init
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
//1
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
//此時的subscriber對象包含了"信號源(signal)"和"訂購者"的全部信息,形成一個完整的信息通道對象
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
//關鍵:在這里觸發了didSubscribe任務塊,并傳入一個"包含了全量信息"的"訂購者"。
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
//這里注冊的任務是直接執行的,schedulingDisposable為全局屬性做一些清理工作。
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
我們先看第一步,這里生成了一個RACPassthroughSubscriber類型的對象,我們看其入參subscriber、self、disposable包含了信號、訂閱者的全部信息。再看self.didSubscribe(subscriber)這句,signal存儲的block在此時才執行,并傳入一個持有了全量信息的的subscriber。
這里我們可以回頭看下篇首的那個問題,在ReactiveCocoa 的實現過程中RACSignal并沒有持有Subscriber, 而是生成的RACPassthroughSubscriber持有了這兩者。這樣一來只要subscriber沒被釋放,異步請求返回后RACSignal自然就不會被釋放了。我們隨便打個斷點看看就能知道了??
到這里我們的信號終于觸發了,但怎樣告知訂閱者呢?從上圖innerSubscriber的4個屬性可以很輕松地看出來。直接調用subscriber的三個接口就是了。理論上通過subscriber你可以對整個信號通道的任意屬性進行修改,但那是不符合游戲規則的??
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
- (void)sendError:(NSError *)e {
@synchronized (self) {
void (^errorBlock)(NSError *) = [self.error copy];
[self.disposable dispose];
if (errorBlock == nil) return;
errorBlock(e);
}
}
- (void)sendCompleted {
@synchronized (self) {
void (^completedBlock)(void) = [self.completed copy];
[self.disposable dispose];
if (completedBlock == nil) return;
completedBlock();
}
}
上面這三個訂閱方法,沒什么好說的了。需要注意下sendNext是沒有調用dispose方法的,所以信號發送完了不要忘記sendCompleted方法的調用哦。也正因如此我們可以多次調用sendNext:哦。
通過源碼的分析,我們很容易看出RACSignal是個不折不扣的熱信號。當然,我們有木有辦法不這樣用呢,當然可以。篇首的問題就是拋出的磚,我們拿到subscriber不立即發消息出去,先存起來,等想要發出信號的時候再通知訂閱者。事實上我們的項目中有不少這樣的用法。如果覺得這樣用很詭異,那我們接著往下看真正的冷信號RACCommand??
<b>二、RACCommand</b>
因為RACCommand中有對RACSubject原理的應用,所以先簡單的說下RACSubject,不廢話,就摳兩段代碼看看你們就懂了
NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
[subscribers addObject:subscriber];
}
- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
NSArray *subscribers;
@synchronized (self.subscribers) {
subscribers = [self.subscribers copy];
}
for (id<RACSubscriber> subscriber in subscribers) {
block(subscriber);
}
}
#pragma mark RACSubscriber
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:value];
}];
}
和上面的RACSignal一個最明顯的區別是:RACSignal每次訂閱都會立即得到反饋(簡單點說每訂閱一次會有一個RACPassthroughSubscriber生成),是一一對應的。而RACSubject會收集多個訂閱者(RACPassthroughSubscriber)后,一次信息的發送多個訂閱者響應。
看上圖,RACCommand其實就比RACSubject多了個Worker而已。接下來先看個command簡單的使用
一樣的先上個簡單的??
//1
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:input];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
}];
}];
}];
//executionSignals: RACSignal
//2
[command.executionSignals subscribeNext:^(RACSignal *signal) {
NSLog(@"訂閱的信號執行"); //x 是signal
}];
//3
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@", x); //x 是data
}];
//4、開始執行命令
[command execute:@"全軍撤退"];
[command execute:@"回防高地"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[command execute:@"發起進攻"];
});
相對來說command就要復雜很多了,第一步我們創建了一個command命令,第二步和第三步又有什么區別,為什么會有這樣的區別呢?待會再說。第四步我們的command能這樣連續發送執行命令么?好了,暈已。還是慢慢看吧。
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock {
NSCParameterAssert(signalBlock != nil);
self = [super init];
if (self == nil) return nil;
//1、創建儲存管理的容器(signal生成block)
_activeExecutionSignals = [[NSMutableArray alloc] init];
_signalBlock = [signalBlock copy];
//2、監聽容器中block元素的改變。
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];
//2、這里還是處理第二步生成的signal
_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;
}
這并不是一段好理解的代碼,需要綁定的東西太多了。你只需要明白這里只做了一件事,生成一個數組,并監聽這個數組的任意改變事件。著重理解下executionSignals,這是一個包含信號的信號。這樣一來,下面這兩句代碼就不難理解了??
[command.executionSignals subscribeNext:^(RACSignal *signal) {
NSLog(@"訂閱的信號執行"); //x 是signal
}];
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@", x); //x 是data
}];
前者是command中管理者拋出來的信號,后者才是訂閱了我們最后生成的那個signal。 command中雖然只存了一個生成信號的block:_signalBlock = [signalBlock copy],調用signalBlock生成signal對象便交由executionSignals管理了。好了,signalBlock是什么時候調用的呢?
- (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];
}
//1
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]];
}
當我們調用execute后,會生成signal,此時我們給executionSignals(RACSignal)訂閱的事件自然會做出相應。但RACSignal中的didSubscribe并未執行。我們可以通過以下兩種方式訂閱這個包含在信號中的信號
[command.executionSignals subscribeNext:^(RACSignal *signal1) {
NSLog(@"訂閱的信號執行1___%p", signal1); //signal p = 0x608000025160
[signal1 subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}];
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@", x); //x = 1
}];
從源碼中我們只需要看出來RACCommand是一個包含了信號的信號,通過對signal的包裝實現了信息的雙向傳遞。
<b>三、bind方法</b>
前面我們分析了信息的一對一傳遞(RACSignal),一對多傳遞(RACSubject),雙向傳遞(RACCommand)。接下來我們來看看多個信號的的綁定
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"bindSignal___a"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *testSignal = [signal1 bind:^RACStreamBindBlock{
//RACStreamBindBlock (^block)(void) 類型的入參
return ^RACSignal* (id value, BOOL *stop){
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:[NSString stringWithFormat:@"%@+bindSignal___b", value]];
[subscriber sendCompleted];
return nil;
}];
};
}];
[testSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
bind api我們一般不直接使用,但會有很多基于bind所生成的接口。在這段示例代碼中,我們創建了signal1并創建了signal2與signal1綁定,生成testSignal后訂閱該signal。
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
/*
* -bind: should:
*
* 1. Subscribe to the original signal of values.
* 2. Any time the original signal sends a value, transform it using the binding block.
* 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
* 4. If the binding block asks the bind to terminate, complete the _original_ signal.
* 5. When _all_ signals complete, send completed to the subscriber.
*
* If any signal sends an error at any point, send that to the subscriber.
*/
//1、這里我們只需要知道傳入的block是一個可以生成signal2的塊。返回的是合并后的signal。這里和command對熱信號的封裝很相似。只是command是以對象的形式存儲,這里是以block的形式存儲
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACStreamBindBlock bindingBlock = block();
//2、bindingBlock 是一個執行即能生成一個signal的block
NSMutableArray *signals = [NSMutableArray arrayWithObject:self];
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
//結束處理
void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) {
BOOL removeDisposable = NO;
@synchronized (signals) {
[signals removeObject:signal];
if (signals.count == 0) {
[subscriber sendCompleted];
[compoundDisposable dispose];
} else {
removeDisposable = YES;
}
}
if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable];
};
//添加處理
void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
@synchronized (signals) {
[signals addObject:signal];
}
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
//6、signal2被訂閱
RACDisposable *disposable = [signal subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(signal, selfDisposable);
}
}];
selfDisposable.disposable = disposable;
};
@autoreleasepool {
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
//3、訂閱合并后的signal后,開始訂閱第一個signal。
RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
// Manually check disposal to handle synchronous errors.
if (compoundDisposable.disposed) return;
BOOL stop = NO;
//4、接收到第一個signal后,調用傳入的block生成第二個signal。
id signal = bindingBlock(x, &stop);
@autoreleasepool {
//5、到這里還有signal2沒有被訂閱了,只能在addSignal(signal)里了
if (signal != nil) addSignal(signal);
if (signal == nil || stop) {
[selfDisposable dispose];
completeSignal(self, selfDisposable);
}
}
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(self, selfDisposable);
}
}];
selfDisposable.disposable = bindingDisposable;
}
return compoundDisposable;
}] setNameWithFormat:@"[%@] -bind:", self.name];
}
我們來看下bind的執行順序,首先調用signal1的bind方法,傳入的并不是signal,而是一個調用即可生成一個signal的block。上面的代碼中直接返回的是合并后的testSignal。當testSignal被訂閱后,上面的這段didSubscribe代碼塊才開始執行,跳過預處理代碼直接看第3步[self subscribeNext:], 這里的self其實是signal1,直接訂閱了signal1,獲取到signal1傳遞來的消息(id x)后,將其當作“signal2生成block”的入參,對應的就是前面示例中的value了。
至此signal2只是生成了,而并未被訂閱。再看第5、6步了,在addSignal 里面直接訂閱了signal2,并通過[subscriber sendNext:x];把消息發送給了testSignal的訂閱者。
這樣一來總結下就成了如下順序了:
生成一個包含[signal1,signal2生成block]處理邏輯的signal->訂閱signal->訂閱signal1->生成signal2->訂閱signal2。
如果多次訂閱testSignal, signal1對象一直是那個signal1,而signal2已不再是那個signal2了。
<b>四、踩坑</b>
1、RACObserver
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { //1
MTModel *model = [[MTModel alloc] init]; // MTModel有一個名為的title的屬性
[subscriber sendNext:model];
[subscriber sendCompleted];
return nil;
}];
self.flattenMapSignal = [signal flattenMap:^RACStream *(MTModel *model) { //2
return RACObserve(model, title);
}];
[self.flattenMapSignal subscribeNext:^(id x) { //3
NSLog(@"subscribeNext - %@", x);
}];
這里就不繞圈子了,RACObserver是會造成循環引用的,直接看RACObserver源碼??
#define RACObserve(TARGET, KEYPATH) \
({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
__weak id target_ = (TARGET); \
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
_Pragma("clang diagnostic pop") \
})
好了,找到self就懂,不廢話。
2、sendComplete的調用問題(或者說RACSignal與RACSubject的持有問題)
//不會有任何問題
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"next"];
return nil;
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
//會導致subject的不釋放
RACSubject *subject = [RACSubject subject];
[subject subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[subject sendNext:@"next"];
subscriber(: RACPassthroughSubscriber)持有property:@[signal, subscriber, disposable];
signal僅持有didSubscribe,并沒有持有任何subscriber
subject(:RACSignal)持有訂閱者數組:@[subscriber1(: RACPassthroughSubscriber),
subscriber2(: RACPassthroughSubscriber),
subscriber3(: RACPassthroughSubscriber)]。
而數組中的每一個訂閱者都是持有signal, subscriber, disposable三項的。
所以其實這里的循環引用一直都在,不要說用weak,這里是必須用強引用的。好了,ReactiveCocoa只能自己處理內存問題了(也必須是這樣)。由于sendNext:事件是可以多次觸發的,唯一的解決方案只有用sendComplelte了。如果還不懂的話,可以回頭看前面對RACSignal和RACSubject源碼的分析就通透了。