研究和使用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羞反、傳入訂閱者,進(jìn)行訂閱
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進(jìn)行調(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 進(jìn)行執(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對其進(jìn)行了重寫惫东。其實這個方法,在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中曼玩,進(jìn)而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ù)組頭部開始论咏,也就是最先添加進(jìn)來的value)
if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
[self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
}
}
}
為了解釋發(fā)生了什么优炬,進(jìn)行如下測試
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 進(jìn)行第三次 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)雅的進(jìn)行傳遞與交互)屁商。但是我們不希望每次對RACSignal的訂閱都重復(fù)執(zhí)行didSubscribe烟很,因為這樣會帶來bug或者性能問題颈墅。
這時,我們要做的就是將冷信號轉(zhuǎn)換為熱信號雾袱,因為熱信號的重復(fù)訂閱是不會帶來這個問題的恤筛。那么如何將冷信號轉(zhuǎn)化為熱信號呢,后面再說吧芹橡。