研究和使用RAC有一段時間了,但一直很懶,不想寫任何東西。決定還是寫一點東西吧,也算是強迫自己整理一下。計劃從最常用的也是RAC的核心(信號)
RACSignal這個類說起,來引出RAC的方方面面:冷熱信號實現(xiàn)及轉(zhuǎn)換、RAC中FRP的體現(xiàn)、rac中的類和協(xié)議(RACDisposable、RACSubscriber、RACCommand、 RACChannel...)、 對信號的處理和控制(combineLatest、bind...)、ReactiveCocoa的設(shè)計思想及結(jié)構(gòu)體系、遇到的一些坑...
RACSignal是什么?
在RAC中RACSignal是一個工廠類,他通過各個工廠方法生成不同的子類實例。同時他繼承基類RACStream(RAC的抽象類,只是定義了處理信號流的方法,子類通過繼承來具體實現(xiàn))。
下面具體討論RACSignal的幾個子類
1. RACDynamicSignal
用法:
//1. 創(chuàng)建信號
RACSignal *dynamicSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"send next"];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@" to release some object because signal had completed or error");
}];
}];
//2. 訂閱信號
[dynamicSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
上面的代碼創(chuàng)建了一個signal, 如果需要dynamicSignal信號工作,我們需要對其訂閱;訂閱之后subscriber就會將"send next"傳遞到x中,因此NSLog的打印就是"send next",如果你使用過RAC一定對此不會陌生。 平時我們所指的signal其實就是RACDynamicSignal(通過createSignal工廠方法創(chuàng)建的一個子類)。還有一些特殊用途的signal,例如RACErrorSignal、RACEmptySignal、RACReturnSignal都是通過工廠方法獲得其實例。
下面通過源碼逐步解析:
1 . 創(chuàng)建信號
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
// 1、創(chuàng)建RACDynamicSignal實例:signal
RACDynamicSignal *signal = [[self alloc] init];
// 2、signal 中保存 傳入的didSubscribe 以便后續(xù)對其調(diào)用
signal->_didSubscribe = [didSubscribe copy];
//tip: rac在創(chuàng)建不同實例的時候都會調(diào)用這個方法,給name屬性賦值(RACStream中唯一具體實現(xiàn)的方法),給不同對象不同的標(biāo)記用于輸出日志等
return [signal setNameWithFormat:@"+createSignal:"];
}
2. 訂閱信號
// subscribeNext方法是對訂閱過程的一個封裝,便于上層調(diào)用
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
//1、創(chuàng)建訂閱者,將nextBlock參數(shù)再次傳遞
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
//2、傳入訂閱者,進行訂閱
return [self subscribe:o];
}
2.1創(chuàng)建訂閱者
// 這里是對訂閱者的初步創(chuàng)建,后面還有對它的二次處理
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
//1、創(chuàng)建RACSubscriber實例:subscriber
RACSubscriber *subscriber = [[self alloc] init];
//2、分別記錄傳入的next、error、completed
subscriber->_next = [next copy];
subscriber->_error = [error copy];
subscriber->_completed = [completed copy];
return subscriber;
}
2.2訂閱
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
/*
* subscriber 被賦給了一個新的RACPassthroughSubscriber實例
* 并將原來的subscriber以及signal本身和disposable作為新的subscriber的實例對象保存
*/
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
if (self.didSubscribe != NULL) {
//下列方法會執(zhí)行傳入的bock參數(shù)(因涉及到RACDisposable和RACScheduler兩大塊,不便具體解釋過程,可以自己看源碼)
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
// 這里對《1.創(chuàng)建信號》中保存的didSubscribe進行調(diào)用,并將subscriber作為block參數(shù)傳入
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
思路整理:首先createSignal方法創(chuàng)建了一個信號dynamicSignal,并把傳入的block參數(shù)記錄在了dynamicSignal的didSubscribe中;然后subscribeNext方法中創(chuàng)建了一個訂閱者 o 用其屬性next保存了從subscribeNext方法傳入的block,接下來subscribeNext中調(diào)用subscribe方法并將 o 作為參數(shù)傳入;而在subscribe方法中執(zhí)行了 self.didSubscribe(subscriber) 其中subscriber就是傳入subscribe方法的o。也就是執(zhí)行了一開始保存在dynamicSignal中的didSubscribe。
也就是執(zhí)行了createSignal方法中傳入的block
^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"send next"];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@" to release some object because signal had completed or error");
}];
}
終于到了最后一步,我們只需要弄清[subscriber sendNext:@"send next"];這一方法的具體實現(xiàn),整個RACDynamicSignal的工作原理就全部揭曉。(sendCompleted 以及 RACDisposable 相關(guān)計劃下次介紹)
sendNext的實現(xiàn)如下:
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
可以看到這里其實就是對上文保存在 o 中的 next 進行執(zhí)行,同時將傳入的 value 再次當(dāng)作 next 的參數(shù)傳入。也就是執(zhí)行了訂閱信號方法subscribeNext 時傳入的 block ,如下
^(id x) {
NSLog(@"%@",x);
}
直到這里,整個信號的 “創(chuàng)建-發(fā)送-訂閱” 的過程全部分析完畢。
這里強調(diào)一下:RACDynamicSignal 中的信號需要被訂閱,然后信號才會被激活。
2. RACSubject
RACSubject 是 RACSignal 的一個子類,用法如下:
//1、創(chuàng)建實例
RACSubject *subject = [RACSubject subject];
//2、訂閱信號
[subject subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//3、發(fā)送信號
[subject sendNext:@"send next"];
和RACSignal不同的是 RACSubject 是先訂閱信號,再發(fā)送信號。RACSignal是冷信號,而RACSubject及其子類是熱信號,這也是為什么RACSubject的創(chuàng)建不是通過RACSignal的工廠方法而是單獨拿出來做為一個體系的原因。下面通過源碼具體分析:
1 . 創(chuàng)建信號
//僅僅只是實例了一個對象
+ (instancetype)subject {
return [[self alloc] init];
}
2. 訂閱信號
//執(zhí)行的還是其父類RACSignal中的方法
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
2.1 創(chuàng)建訂閱者
// 這里和RACSignal中也是一樣,執(zhí)行的RACSubscriber中的方法
+ (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;
}
2.2 訂閱
// 這里開始不同,RACSubject對其進行了重寫。其實這個方法,在RACSignal中沒有實現(xiàn),RACDynamicSignal也是執(zhí)行的RACDynamicSignal中重寫的方法
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
// 這里和RACDynamicSignal中不同,RACSubject持有一個數(shù)組subscribers,來儲存所有的訂閱者,對subject的每一次訂閱都會將訂閱者加入該數(shù)組保存起來
NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
[subscribers addObject:subscriber];
}
return [RACDisposable disposableWithBlock:^{
@synchronized (subscribers) {
// Since newer subscribers are generally shorter-lived, search
// starting from the end of the list.
NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
return obj == subscriber;
}];
if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
}
}];
}
3. 發(fā)送信號
// 循環(huán)遍歷subscribers數(shù)組,逐個執(zhí)行:[subscriber sendNext:value]
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:value];
}];
}
- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
NSArray *subscribers;
@synchronized (self.subscribers) {
subscribers = [self.subscribers copy];
}
for (id<RACSubscriber> subscriber in subscribers) {
block(subscriber);
}
}
和RACSignal不同的是:RACSubject 通過subscribeNext方法將訂閱者保存在subscribers數(shù)組中,供sendNext方法調(diào)用;而RACSignal是先保存一個didSubscribe對象,然后通過subscribeNext方法創(chuàng)建一個訂閱者subscriber作為參數(shù)傳入didSubscribe中,進而subscriber才能發(fā)送信號。
簡言之:RACSubject會保存所有subscriber(狀態(tài)),sendNext只是對其操作。RACSignal 在被訂閱后才會生成subscriber,并立即對其操作。
這也是冷信號和熱信號的區(qū)別(冷信號需要通過訂閱來激活)
3. RACReplaySubject
RACReplaySubject 是 RACSubject 的一個子類,他們都是熱信號,那么他們有什么不同呢,還是從源碼入手
//1、創(chuàng)建實例
RACReplaySubject *subject = [RACReplaySubject replaySubjectWithCapacity:2];
//2、訂閱信號
[subject subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//3、發(fā)送信號
[subject sendNext:@"send next"];
1 . 創(chuàng)建信號
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity {
return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity];
}
- (instancetype)initWithCapacity:(NSUInteger)capacity {
self = [super init];
if (self == nil) return nil;
// 這里會用一個屬性capacity 將 傳入的數(shù)記錄下來,下面會有這個數(shù)的用途
_capacity = capacity;
_valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]);
return self;
}
// 也可以直接對其實例,capacity為最大無符號整數(shù)
- (instancetype)init {
// const NSUInteger RACReplaySubjectUnlimitedCapacity = NSUIntegerMax;
return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity];
}
2. 訂閱信號
// 也是在subscribe方法發(fā)生變化,走的是RACReplaySubject中的方法
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
@synchronized (self) {
// 這里會從valuesReceived里sendNext未被執(zhí)行過value,第三步中會有 valuesReceived 的操作
for (id value in self.valuesReceived) {
if (compoundDisposable.disposed) return;
[subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
}
if (compoundDisposable.disposed) return;
if (self.hasCompleted) {
[subscriber sendCompleted];
} else if (self.hasError) {
[subscriber sendError:self.error];
} else {
RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
[compoundDisposable addDisposable:subscriptionDisposable];
}
}
}];
[compoundDisposable addDisposable:schedulingDisposable];
return compoundDisposable;
}
3. 發(fā)送信號
- (void)sendNext:(id)value {
@synchronized (self) {
// 每次將發(fā)送過的value添加到valuesReceived中記錄下來
[self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
// 調(diào)用父類也就是RACSubject的sendNext
[super sendNext:value];
// 通過初始化時的capacity值截取valuesReceived的元素(從數(shù)組頭部開始,也就是最先添加進來的value)
if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
[self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
}
}
}
為了解釋發(fā)生了什么,進行如下測試
RACReplaySubject *subject = [RACReplaySubject subject];
[subject subscribeNext:^(id x) {
NSLog(@"%@--1",x);
}];
[subject subscribeNext:^(id x) {
NSLog(@"%@--2",x);
}];
[subject sendNext:@"send next1"];
[subject sendNext:@"send next2"];
[subject subscribeNext:^(id x) {
NSLog(@"%@--3",x);
}];
日志:
//1
2017-03-23 22:03:03.722 testdemo[3509:143199] send next1--1
//2
2017-03-23 22:03:03.722 testdemo[3509:143199] send next1--2
//3
2017-03-23 22:03:03.722 testdemo[3509:143199] send next2--1
//4
2017-03-23 22:03:03.723 testdemo[3509:143199] send next2--2
//5
2017-03-23 22:03:03.723 testdemo[3509:143199] send next1--3
//6
2017-03-23 22:03:03.723 testdemo[3509:143199] send next2--3
分析:
- [subject sendNext:@"send next1"] 毫無疑問會打印 1 2 兩條日志,同時將value存入了valuesReceived;
- [subject sendNext:@"send next2"] 會打印 3 4 兩條日志,也同時將value存入了valuesReceived,到此valuesReceived里就存有兩個value了;
- 對subject 進行第三次 subscribeNext 時,因為valuesReceived里是有值的,所以會執(zhí)行兩次
subscriber sendNext,也就是打印出日志 5 和 6 。
RACReplaySubject 會將每次sendNext的值記錄到valuesReceived(根據(jù)capacity大小會移除最先添加的元素),之后對RACReplaySubject的訂閱會將valuesReceived里的記錄通過訂閱者來發(fā)送。** 這也就是RACReplaySubject的區(qū)別**
對于RACSignal的介紹到此結(jié)束,我們可以發(fā)現(xiàn)對RACDynamicSignal的每次訂閱都會重復(fù)執(zhí)行didSubscribe,而RACSubject則不會。
在我們平時使用時,很多場景是需要用到 RACDynamicSignal 的。
例如:我們會把數(shù)據(jù)處理、網(wǎng)絡(luò)請求、UI刷新等等一系列操作放在didSubscribe里(這也是RAC的宗旨啊,將平時的各種操作都轉(zhuǎn)化為炫酷的信號的形式,優(yōu)雅的進行傳遞與交互)。但是我們不希望每次對RACSignal的訂閱都重復(fù)執(zhí)行didSubscribe,因為這樣會帶來bug或者性能問題。
這時,我們要做的就是將冷信號轉(zhuǎn)換為熱信號,因為熱信號的重復(fù)訂閱是不會帶來這個問題的。那么如何將冷信號轉(zhuǎn)化為熱信號呢,后面再說吧。