原文
請(qǐng)您閱讀原文 『狀態(tài)』驅(qū)動(dòng)的世界:ReactiveCocoa,作者 Draven牌柄。聲明:本文只用做RAC相關(guān)知識(shí)點(diǎn)梳理蒋失,不做他用正什。
RAC 設(shè)計(jì)思維
去中心化的方式丑慎,能簡(jiǎn)化整個(gè)系統(tǒng)的構(gòu)造,使得各個(gè)組件只需要關(guān)心狀態(tài)究珊,以及狀態(tài)對(duì)應(yīng)的動(dòng)作薪者;不再需要一個(gè)中樞系統(tǒng)來(lái)組織、管理其它的組件剿涮,并負(fù)責(zé)大多數(shù)的業(yè)務(wù)邏輯言津。這種自底向下的、狀態(tài)驅(qū)動(dòng)的構(gòu)建方式能夠使用多個(gè)較小的組件取试,減少臃腫的中樞出現(xiàn)的可能性悬槽,從而降低系統(tǒng)的復(fù)雜度。
ReactiveCocoa
對(duì)于狀態(tài)的理解與上述瞬浓,將原有的各種設(shè)計(jì)模式初婆,包括代理、Target/Action猿棉、通知中心以及觀察者模式各種『輸入』磅叛,都抽象成了信號(hào)(也可以理解為狀態(tài)流)讓單一的組件能夠?qū)ψ约旱捻憫?yīng)動(dòng)作進(jìn)行控制,簡(jiǎn)化了視圖控制器的負(fù)擔(dān)铺根。
RACSignal 簡(jiǎn)介
RACSignal
其實(shí)是抽象類 RACStream
的子類宪躯,在整個(gè) ReactiveObjc
工程中有另一個(gè)類 RACSequence
也繼承自抽象類 RACStream
RACSignal
可以說(shuō)是 ReactiveCocoa 中的核心類乔宿,也是最重要的概念位迂,整個(gè)框架圍繞著 RACSignal
的概念進(jìn)行組織,對(duì) RACSignal
最簡(jiǎn)單的理解就是它表示一連串的狀態(tài):
在狀態(tài)改變時(shí)详瑞,對(duì)應(yīng)的訂閱者 RACSubscriber
就會(huì)收到通知執(zhí)行相應(yīng)的指令掂林,在 ReactiveCocoa
的世界中所有的消息都是通過(guò)信號(hào)的方式來(lái)傳遞的,原有的設(shè)計(jì)模式都會(huì)簡(jiǎn)化為一種模型坝橡。
RACStream
RACStream
作為抽象類本身不提供方法的實(shí)現(xiàn)泻帮,其實(shí)現(xiàn)內(nèi)部原生提供的而方法都是抽象方法,會(huì)在調(diào)用時(shí)直接拋出異常:
+ (__kindof RACStream *)empty {
NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)];
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil];
}
- (__kindof RACStream *)bind:(RACStreamBindBlock (^)(void))block;
+ (__kindof RACStream *)return:(id)value;
- (__kindof RACStream *)concat:(RACStream *)stream;
- (__kindof RACStream *)zipWith:(RACStream *)stream;
上面的這些抽象方法都需要子類覆寫计寇,不過(guò) RACStream
在 Operations
分類中使用上面的抽象方法提供了豐富的內(nèi)容锣杂,比如說(shuō) -flattenMap:
方法:
- (__kindof RACStream *)flattenMap:(__kindof RACStream * (^)(id value))block {
Class class = self.class;
return [[self bind:^{
return ^(id value, BOOL *stop) {
id stream = block(value) ?: [class empty];
NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream);
return stream;
};
}] setNameWithFormat:@"[%@] -flattenMap:", self.name];
}
其他方法比如-skip:脂倦、-take:、-ignore:
等等實(shí)例方法都構(gòu)建在這些抽象方法之上元莫,只要子類覆寫了所有抽象方法就能自動(dòng)獲得所有的 Operation
分類中的方法赖阻。
信號(hào)的創(chuàng)建過(guò)程十分簡(jiǎn)單,-createSignal: 是推薦的創(chuàng)建信號(hào)的方法踱蠢,方法其實(shí)只做了一次轉(zhuǎn)發(fā):
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
該方法其實(shí)只是創(chuàng)建了一個(gè) RACDynamicSignal
實(shí)例并保存了傳入的 didSubscribe
代碼塊火欧,在每次有訂閱者訂閱當(dāng)前信號(hào)時(shí),都會(huì)執(zhí)行一遍茎截,向訂閱者發(fā)送消息苇侵。
RACSignal 類簇
雖然 -createSignal: 的方法簽名上返回的是 RACSignal
對(duì)象的實(shí)例,但是實(shí)際上這里返回的是 RACDynamicSignal
企锌,也就是 RACSignal 的子類
榆浓;同樣,在 ReactiveCocoa
中也有很多其他的 RACSignal 子類
霎俩。
使用類簇的方式設(shè)計(jì)的 RACSignal
在創(chuàng)建實(shí)例時(shí)可能會(huì)返回 RACDynamicSignal
哀军、RACEmptySignal
、RACErrorSignal
和 RACReturnSignal
對(duì)象:
其實(shí)這幾種子類并沒(méi)有對(duì)原有的 RACSignal
做出太大的改變打却,它們的創(chuàng)建過(guò)程也不是特別的復(fù)雜杉适,只需要調(diào)用 RACSignal
不同的類方法:
RACSignal 只是起到了一個(gè)代理的作用,最后的實(shí)現(xiàn)過(guò)程還是會(huì)指向?qū)?yīng)的子類:
+ (RACSignal *)error:(NSError *)error {
return [RACErrorSignal error:error];
}
+ (RACSignal *)empty {
return [RACEmptySignal empty];
}
+ (RACSignal *)return:(id)value {
return [RACReturnSignal return:value];
}
RAC 信號(hào)訂閱流程
創(chuàng)建信號(hào) RACSignal 柳击,RACSignal 調(diào)用 -subscribeNext: 方法返回一個(gè) RACDisposable猿推,在訂閱過(guò)程中生成了一個(gè) RACSubscriber 對(duì)象,向這個(gè)對(duì)象發(fā)送消息 -sendNext: 時(shí)捌肴,就會(huì)向所有的訂閱者發(fā)送消息蹬叭。
信號(hào)的訂閱
訂閱者可以選擇自己想要感興趣的信息類型 next/error/completed
進(jìn)行關(guān)注,并在對(duì)應(yīng)的信息發(fā)生時(shí)調(diào)用 block
進(jìn)行處理回調(diào)状知。
所有的方法其實(shí)只是對(duì) nextBlock
秽五、completedBlock
以及 errorBlock
的組合,這里以其中最長(zhǎng)的 -subscribeNext:error:completed:
方法的實(shí)現(xiàn)為例(也只需要介紹這一個(gè)方法):
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock {
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock];
return [self subscribe:o];
}
方法中傳入的所有 block
參數(shù)都應(yīng)該是非空的饥悴。
拿到了傳入的 block 之后坦喘,使用 +subscriberWithNext:error:completed:
初始化一個(gè) RACSubscriber
對(duì)象的實(shí)例:
+ (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;
}
在拿到這個(gè)對(duì)象之后,調(diào)用 RACSignal
的 -subscribe:
方法傳入訂閱者對(duì)象:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCAssert(NO, @"This method must be overridden by subclasses");
return nil;
}
RACSignal
類中其實(shí)并沒(méi)有實(shí)現(xiàn)這個(gè)實(shí)例方法西设,需要在上文提到的四個(gè)子類對(duì)這個(gè)方法進(jìn)行覆寫瓣铣,這里僅分析 RACDynamicSignal
中的方法:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
return disposable;
}
RACPassthroughSubscriber
就像它的名字一樣,只是對(duì)上面創(chuàng)建的訂閱者對(duì)象進(jìn)行簡(jiǎn)單的包裝贷揽,將所有的消息轉(zhuǎn)發(fā)給內(nèi)部的 innerSubscriber
棠笑,也就是傳入的 RACSubscriber
對(duì)象:
- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
self = [super init];
_innerSubscriber = subscriber;
_signal = signal;
_disposable = disposable;
[self.innerSubscriber didSubscribeWithDisposable:self.disposable];
return self;
}
如果直接簡(jiǎn)化 -subscribe:
方法的實(shí)現(xiàn),你可以看到一個(gè)看起來(lái)極為敷衍的代碼:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
return self.didSubscribe(subscriber);
}
總而言之禽绪,信號(hào)的訂閱過(guò)程就是初始化 RACSubscriber
對(duì)象蓖救,然后執(zhí)行 didSubscribe
代碼塊的過(guò)程生成 disposable
洪规。
信息的發(fā)送
在 RACSignalBindBlock 中,訂閱者可以根據(jù)自己的興趣選擇自己想要訂閱哪種消息循捺;我們也可以按需發(fā)送三種消息:
訂閱的回收過(guò)程
在創(chuàng)建信號(hào)時(shí)淹冰,我們向 -createSignal:
方法中傳入了 didSubscribe
信號(hào),這個(gè) block
在執(zhí)行結(jié)束時(shí)會(huì)返回一個(gè) RACDisposable
對(duì)象巨柒,用于在訂閱結(jié)束時(shí)進(jìn)行必要的清理樱拴,同樣也可以用于取消因?yàn)橛嗛唲?chuàng)建的正在執(zhí)行的任務(wù)。
而處理這些事情的核心類就是 RACDisposable 以及它的子類:
這篇文章中主要關(guān)注的是左側(cè)的三個(gè)子類洋满,當(dāng)然 RACDisposable 的子類不止這三個(gè)晶乔,還有用于處理 KVO 的 RACKVOTrampoline,不過(guò)在這里我們不會(huì)討論這個(gè)類的實(shí)現(xiàn)牺勾。
RACDisposable
在繼續(xù)分析討論訂閱的回收過(guò)程之前正罢,筆者想先對(duì) RACDisposable
進(jìn)行簡(jiǎn)要的剖析和介紹:
類 RACDisposable
是以 _disposeBlock
為核心進(jìn)行組織的,幾乎所有的方法以及屬性其實(shí)都是對(duì) _disposeBlock
進(jìn)行的操作驻民。
關(guān)于 _disposeBlock 中的 self
這一小節(jié)的內(nèi)容是可選的翻具,跳過(guò)不影響整篇文章閱讀的連貫性。
_disposeBlock
是一個(gè)私有的指針變量回还,當(dāng) void (^)(void)
類型的 block 被傳入之后都會(huì)轉(zhuǎn)換成 CoreFoundation 中的類型并以 void *
的形式存入 _disposeBlock
中:
+ (instancetype)disposableWithBlock:(void (^)(void))block {
return [[self alloc] initWithBlock:block];
}
- (instancetype)initWithBlock:(void (^)(void))block {
self = [super init];
_disposeBlock = (void *)CFBridgingRetain([block copy]);
OSMemoryBarrier();
return self;
}
奇怪的是裆泳,_disposeBlock
中不止會(huì)存儲(chǔ)代碼塊 block
,還有可能存儲(chǔ)橋接之后的self
:
- (instancetype)init {
self = [super init];
_disposeBlock = (__bridge void *)self;
OSMemoryBarrier();
return self;
}
這里柠硕,剛開(kāi)始看到可能會(huì)覺(jué)得比較奇怪工禾,有兩個(gè)疑問(wèn)需要解決:
- 為什么要提供一個(gè) -init 方法來(lái)初始化
RACDisposable
對(duì)象? - 為什么要向
_disposeBlock
中傳入當(dāng)前對(duì)象蝗柔?
對(duì)于 RACDisposable
來(lái)說(shuō)闻葵,雖然一個(gè)不包含 _disposeBlock
的對(duì)象沒(méi)什么太多的意義,但是對(duì)于 RACSerialDisposable
等子類來(lái)說(shuō)癣丧,卻不完全是這樣槽畔,因?yàn)?RACSerialDisposable
在 -dispose
時(shí),并不需要執(zhí)行 disposeBlock
胁编,這樣就浪費(fèi)了內(nèi)存和 CPU 時(shí)間厢钧;但是同時(shí)我們需要一個(gè)合理的方法準(zhǔn)確地判斷當(dāng)前對(duì)象的 isDisposed
:
所以,使用向 _disposeBlock
中傳入 NULL
的方式來(lái)判斷 isDisposed
掏呼;在 -init
調(diào)用時(shí)傳入 self
而不是 NULL
防止?fàn)顟B(tài)被誤判坏快,這樣就在不引入其他實(shí)例變量铅檩、增加對(duì)象的設(shè)計(jì)復(fù)雜度的同時(shí)憎夷,解決了這兩個(gè)問(wèn)題。
如果仍然不理解上述的兩個(gè)問(wèn)題昧旨,在這里舉一個(gè)錯(cuò)誤的例子拾给,如果 _disposeBlock
在使用時(shí)只傳入 NULL
或者 block
祥得,那么在 RACCompoundDisposable
初始化時(shí),是應(yīng)該向 _disposeBlock
中傳入什么呢蒋得?
- 傳入
NULL
會(huì)導(dǎo)致在初始化之后isDisposed == YES
级及,然而當(dāng)前對(duì)象根本沒(méi)有被回收; - 傳入
block
會(huì)導(dǎo)致無(wú)用的 block 的執(zhí)行额衙,浪費(fèi)內(nèi)存以及 CPU 時(shí)間饮焦;
這也就是為什么要引入 self
來(lái)作為 _disposeBlock
內(nèi)容的原因。
-dispose: 方法的實(shí)現(xiàn)
這個(gè)只有不到 20 行的 -dispose:
方法已經(jīng)是整個(gè) RACDisposable
類中最復(fù)雜的方法了:
- (void)dispose {
void (^disposeBlock)(void) = NULL;
while (YES) {
void *blockPtr = _disposeBlock;
if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) {
if (blockPtr != (__bridge void *)self) {
disposeBlock = CFBridgingRelease(blockPtr);
}
break;
}
}
if (disposeBlock != nil) disposeBlock();
}
RACSerialDisposable
RACSerialDisposable
是一個(gè)用于持有 RACDisposable
的容器窍侧,它一次只能持有一個(gè)RACDisposable
的實(shí)例县踢,并可以原子地?fù)Q出容器中保存的對(duì)象:
- (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable {
RACDisposable *existingDisposable;
BOOL alreadyDisposed;
pthread_mutex_lock(&_mutex);
alreadyDisposed = _disposed;
if (!alreadyDisposed) {
existingDisposable = _disposable;
_disposable = newDisposable;
}
pthread_mutex_unlock(&_mutex);
if (alreadyDisposed) {
[newDisposable dispose];
return nil;
}
return existingDisposable;
}
線程安全的 RACSerialDisposable
使用 pthred_mutex_t
互斥鎖來(lái)保證在訪問(wèn)關(guān)鍵變量時(shí)不會(huì)出現(xiàn)線程競(jìng)爭(zhēng)問(wèn)題。
-dispose
方法的處理也十分簡(jiǎn)單:
- (void)dispose {
RACDisposable *existingDisposable;
pthread_mutex_lock(&_mutex);
if (!_disposed) {
existingDisposable = _disposable;
_disposed = YES;
_disposable = nil;
}
pthread_mutex_unlock(&_mutex);
[existingDisposable dispose];
}
使用鎖保證線程安全伟件,并在內(nèi)部的 _disposable
換出之后在執(zhí)行 -dispose
方法對(duì)訂閱進(jìn)行處理硼啤。
RACCompoundDisposable
與 RACSerialDisposable
只負(fù)責(zé)一個(gè) RACDisposable
對(duì)象的釋放不同;RACCompoundDisposable
同時(shí)負(fù)責(zé)多個(gè) RACDisposable
對(duì)象的釋放斧账。
相比于只管理一個(gè) RACDisposable
對(duì)象的 RACSerialDisposable
谴返,RACCompoundDisposable
由于管理多個(gè)對(duì)象,其實(shí)現(xiàn)更加復(fù)雜咧织,而且為了性能和內(nèi)存占用之間的權(quán)衡嗓袱,其實(shí)現(xiàn)方式是通過(guò)持有兩個(gè)實(shí)例變量:
@interface RACCompoundDisposable () {
...
RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount];
CFMutableArrayRef _disposables;
...
}
在對(duì)象持有的 RACDisposable
不超過(guò) RACCompoundDisposableInlineCount
時(shí),都會(huì)存儲(chǔ)在_inlineDisposables
數(shù)組中习绢,而更多的實(shí)例都會(huì)存儲(chǔ)在 _disposables
中:
RACCompoundDisposable
在使用 -initWithDisposables:
初始化時(shí)索抓,會(huì)初始化兩個(gè) RACDisposable
的位置用于加速銷毀訂閱的過(guò)程,同時(shí)為了不浪費(fèi)內(nèi)存空間毯炮,在默認(rèn)情況下只占用兩個(gè)位置:
- (instancetype)initWithDisposables:(NSArray *)otherDisposables {
self = [self init];
[otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) {
self->_inlineDisposables[index] = disposable;
if (index == RACCompoundDisposableInlineCount - 1) *stop = YES;
}];
if (otherDisposables.count > RACCompoundDisposableInlineCount) {
_disposables = RACCreateDisposablesArray();
CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount);
CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range);
}
return self;
}
如果傳入的 otherDisposables
多于 RACCompoundDisposableInlineCount
逼肯,就會(huì)創(chuàng)建一個(gè)新的 CFMutableArrayRef
引用,并將剩余的 RACDisposable
全部傳入這個(gè)數(shù)組中桃煎。
在 RACCompoundDisposable
中另一個(gè)值得注意的方法就是 -addDisposable:
- (void)addDisposable:(RACDisposable *)disposable {
if (disposable == nil || disposable.disposed) return;
BOOL shouldDispose = NO;
pthread_mutex_lock(&_mutex);
{
if (_disposed) {
shouldDispose = YES;
} else {
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
if (_inlineDisposables[i] == nil) {
_inlineDisposables[i] = disposable;
goto foundSlot;
}
}
if (_disposables == NULL) _disposables = RACCreateDisposablesArray();
CFArrayAppendValue(_disposables, (__bridge void *)disposable);
foundSlot:;
}
}
pthread_mutex_unlock(&_mutex);
if (shouldDispose) [disposable dispose];
}
在向 RACCompoundDisposable
中添加新的 RACDisposable
對(duì)象時(shí)篮幢,會(huì)先嘗試在 _inlineDisposables
數(shù)組中尋找空閑的位置,如果沒(méi)有找到为迈,就會(huì)加入到 _disposables
中三椿;但是,在添加 RACDisposable
的過(guò)程中也難免遇到當(dāng)前 RACCompoundDisposable
已經(jīng) dispose
的情況葫辐,而這時(shí)就會(huì)直接-
dispose` 剛剛加入的對(duì)象搜锰。