
本文是投稿文章,作者:聽榆大叔(博客)
開車不需要知道離合器是怎麼工作的,但如果知道離合器原理,那麼車子可以開得更平穩。
ReactiveCocoa 是一個重型的 FRP 框架,內容十分豐富,它使用了大量內建的 block,這使得其有強大的功能的同時,內部源碼也比較復雜。本文研究的版本是2.4.4,小版本間的差別不是太大,無需擔心此問題。 這裡只探究其核心 RACSignal 源碼及其相關部分。本文不會詳細解釋裡面的代碼,重點在於討論那些核心代碼是怎麼來的。文本難免有不正確的地方,請不吝指教,非常感謝。
@protocol RACSubscriber
信號是一個異步數據流,即一個將要發生的以時間為序的事件序列,它能發射出三種不同的東西:value、error、completed。咱們能異步地捕獲這些事件:監聽信號,針對其發出的三種東西進行操作。“監聽”信息的行為叫做 訂閱(subscriber)。我們定義的操作就是觀察者,這個被“監聽”的信號就是被觀察的主體(subject) 。其實,這正是“觀察者”設計模式!
RAC 針對這個訂閱行為定義了一個協議:RACSubscriber。RACSubscriber 協議是與 RACSignal 打交道的唯一方式。咱們先不探究 RACSignal 的內容,而是先研究下 RACSubscriber 是怎麼回事。
先來看下 RACSubscriber 的定義:
// 用於從 RACSignal 中直接接收 values 的對象 @protocol RACSubscriber @required /// 發送下一個 value 給 subscribers。value 可以為 nil。 - (void)sendNext:(id)value; /// 發送 error 給 subscribers。 error 可以為 nil。 /// /// 這會終結整個訂閱行為,而且接下來也無法再訂閱任何信號了。 - (void)sendError:(NSError *)error; /// 發送 completed 給 subscribers。 /// /// 這會終結整個訂閱行為,而且接下來也無法再訂閱任何信號了。 - (void)sendCompleted; /// 現在重要的是上面三個,先別管這個,忽略掉。 - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable; @end
1、NLSubscriber
咱們自己來實現這個協議看看(本文自定義的類都以 “NL” 開頭,以視區別):
// NLSubscriber.h
@interface NLSubscriber : NSObject @end
// NLSubscriber.m
@implementation NLSubscriber
- (void)sendNext:(id)value {
NSLog(@"%s value:%@", sel_getName(_cmd), value);
}
- (void)sendCompleted {
NSLog(@"%s", sel_getName(_cmd));
}
- (void)sendError:(NSError *)error {
NSLog(@"%s error:%@", sel_getName(_cmd), error);
}
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable {
// to nothing
}
@end現在咱們這個類只關心 sendNext: 、 sendError: 和 sendCompleted。本類的實現只是簡單的打印一些數據。那怎麼來使用這個訂閱者呢?RACSignal 類提供了接口來讓實現了 RACSubscriber 協議的訂閱者訂閱信號:
@interface RACSignal (Subscription) /* * `subscriber` 訂閱 receiver 的變化。由 receiver 決定怎麼給 subscriber 發送事件。 *簡單來說,就是由這個被訂閱的信號來給訂閱者 subscriber 發送 `sendNext:` 等消息。 */ - (RACDisposable *)subscribe:(id)subscriber; @end
用定時器信號來試試看:
/** * @brief 創建一個定時器信號,每三秒發出一個當時日期值。一共發5次。 */ RACSignal *signalInterval = [RACSignal interval:3.0 onScheduler:[RACScheduler mainThreadScheduler]]; signalInterval = [signalInterval take:5]; NLSubscriber *subscriber = [[NLSubscriber alloc] init]; /** * @brief 用訂閱者 subscriber 訂閱定時器信號 */ [signalInterval subscribe:subscriber];
下面是輸出結果:
2015-08-15 17:45:02.612 RACPraiseDemo[738:59818] sendNext: value:2015-08-15 09:45:02 +0000 2015-08-15 17:45:05.612 RACPraiseDemo[738:59818] sendNext: value:2015-08-15 09:45:05 +0000 2015-08-15 17:45:08.615 RACPraiseDemo[738:59818] sendNext: value:2015-08-15 09:45:08 +0000 2015-08-15 17:45:11.613 RACPraiseDemo[738:59818] sendNext: value:2015-08-15 09:45:11 +0000 2015-08-15 17:45:14.615 RACPraiseDemo[738:59818] sendNext: value:2015-08-15 09:45:14 +0000 2015-08-15 17:45:14.615 RACPraiseDemo[738:59818] sendCompleted
2、改進NLSubscriber
現在的這個訂閱者類 NLSubscriber 除了打印打東西外,啥也干不了,更別說復用了,如果針對所有的信號都寫一個訂閱者那也太痛苦了,甚至是不太可能的事。
咱們來改進一下,做到如下幾點:
a.實現 RACSubscriber 協議
b.提供與 RACSubscriber 相 對應的 、可選的 、可配的接口。
沒錯,這正是一個適配器!
第2點的要求可不少,那怎麼才能做到這一點呢?還好,OC 中有 block !咱們可以將 RACSubscriber 協議中的三個方法轉為三個 block:
- (void)sendNext:(id)value; ----> void (^next)(id value); - (void)sendError:(NSError *)error; ----> void (^error)(NSError *error); - (void)sendCompleted; ----> void (^completed)(void);
改進目標和改進方向都有了,那咱們來看看改進後的的樣子:
// 頭文件
/**
* @brief 基於 block 的訂閱者
*/
@interface NLSubscriber : NSObject /**
* @brief 創建實例
*/
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed;
@end
// 實現文件
@interface NLSubscriber ()
@property (nonatomic, copy) void (^next)(id value);
@property (nonatomic, copy) void (^error)(NSError *error);
@property (nonatomic, copy) void (^completed)(void);
@end
@implementation NLSubscriber
#pragma mark Lifecycle
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
NLSubscriber *subscriber = [[self alloc] init];
subscriber->_next = [next copy];
subscriber->_error = [error copy];
subscriber->_completed = [completed copy];
return subscriber;
}
#pragma mark RACSubscriber
- (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];
if (errorBlock == nil) return;
errorBlock(e);
}
}
- (void)sendCompleted {
@synchronized (self) {
void (^completedBlock)(void) = [self.completed copy];
if (completedBlock == nil) return;
completedBlock();
}
}
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable {
// to nothing
}
@end現在來試試看這個改進版,還是上面那個定時器的例子:
/**
* @brief 創建一個定時器信號,每三秒發出一個當時日期值。一共發5次。
*/
RACSignal *signalInterval = [RACSignal interval:3.0 onScheduler:[RACScheduler mainThreadScheduler]];
signalInterval = [signalInterval take:5];
NLSubscriber *subscriber = [NLSubscriber subscriberWithNext:^(id x) {
NSLog(@"next:%@", x);
} error:nil completed:^{
NSLog(@"completed");
}];
/**
* @brief 用訂閱者 subscriber 訂閱定時器信號
*/
[signalInterval subscribe:subscriber];輸出結果如下:
2015-08-15 19:50:43.355 RACPraiseDemo[870:116551] next:2015-08-15 11:50:43 +0000 2015-08-15 19:50:46.358 RACPraiseDemo[870:116551] next:2015-08-15 11:50:46 +0000 2015-08-15 19:50:49.355 RACPraiseDemo[870:116551] next:2015-08-15 11:50:49 +0000 2015-08-15 19:50:52.356 RACPraiseDemo[870:116551] next:2015-08-15 11:50:52 +0000 2015-08-15 19:50:55.356 RACPraiseDemo[870:116551] next:2015-08-15 11:50:55 +0000 2015-08-15 19:50:55.356 RACPraiseDemo[870:116551] completed
輸出結果沒什麼變化,但是訂閱者的行為終於受到咱們的撐控了。再也不用為了一個信號而去實現 RACSubscriber 協議了,只需要拿出 NLSubscriber 這個適配器,再加上咱們想要的自定義的行為即可。如果對信號發出的某個事件不感興趣,直接傳個 nil 可以了,例如上面例子的 error: ,要知道, RACSubscriber 協議中的所有方法都是 @required 的。NLSubscriber 大大方便了我們的工作。
那還以再改進嗎?
3、RACSignal 類別之 Subscription
有沒有可能把 NLSubscriber 隱藏起來呢?畢竟作為一個信號的消費者,需要了解的越少就越簡單,用起來也就越方便。咱們可以通過 OC 中的類別方式,給 RACSignal 加個類別(nl_Subscription),將訂閱操作封裝到這個信號類中。這樣,對於使用這個類的客戶而言,甚至不知道訂閱者的存在。
nl_Subscription 類別代碼如下:
// .h
#import "RACSignal.h"
@interface RACSignal (nl_Subscription)
- (void)nl_subscribeNext:(void (^)(id x))nextBlock;
- (void)nl_subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock;
- (void)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
- (void)nl_subscribeError:(void (^)(NSError *error))errorBlock;
- (void)nl_subscribeCompleted:(void (^)(void))completedBlock;
- (void)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock;
- (void)nl_subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
@end
// .m
#import "RACSignal+nl_Subscription.h"
#import "NLSubscriber.h"
@implementation RACSignal (nl_Subscription)
- (void)nl_subscribeNext:(void (^)(id x))nextBlock {
[self nl_subscribeNext:nextBlock error:nil completed:nil];
}
- (void)nl_subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock {
[self nl_subscribeNext:nextBlock error:nil completed:completedBlock];
}
- (void)nl_subscribeError:(void (^)(NSError *error))errorBlock {
[self nl_subscribeNext:nil error:errorBlock completed:nil];
}
- (void)nl_subscribeCompleted:(void (^)(void))completedBlock {
[self nl_subscribeNext:nil error:nil completed:completedBlock];
}
- (void)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock {
[self nl_subscribeNext:nextBlock error:errorBlock completed:nil];
}
- (void)nl_subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock {
[self nl_subscribeNext:nil error:errorBlock completed:completedBlock];
}
- (void)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock {
NLSubscriber *subscriber = [NLSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock];
[self subscribe:subscriber];
}
@end在這個類別中,將信號的 next:、error: 和 completed 以及這三個事件的組合都以 block 的形式封裝起來,從以上代碼中可以看出,這些方法最終調用的還是 - (void)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock; 方法,而它則封裝了訂閱者 NLSubsciber。
通過這麼個小小的封裝,客戶使用起來就極其方便了:
/**
* @brief 創建一個自定義的信號。
* 這個信號在被訂閱時,會發送一個當前的日期值;
* 再過三秒後,再次發送此時的日期值;
* 最後,再發送完成事件。
*/
RACSignal *signalInterval = [RACSignal createSignal:^(id subscriber) {
[subscriber sendNext:[NSDate date]];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:[NSDate date]];
[subscriber sendCompleted];
});
return (id)nil;
}];
[signalInterval nl_subscribeNext:^(id x) {
NSLog(@"next:%@", x);
} error:^(NSError *error) {
NSLog(@"error:%@", error);
} completed:^{
NSLog(@"completed");
}];輸出如下:
2015-08-16 23:29:44.406 RACPraiseDemo[653:32675] next:2015-08-16 15:29:44 +0000 2015-08-16 23:29:47.701 RACPraiseDemo[653:32675] next:2015-08-16 15:29:47 +0000 2015-08-16 23:29:47.701 RACPraiseDemo[653:32675] completed
本例並沒有采用之前的 “定時器信號”,而是自己創建的信號,當有訂閱者到來時,由這個信號來決定在什麼時候發送什麼事件。這個例子裡發送的事件的邏輯請看代碼裡的注釋。
看到這裡,是不是很熟悉了?有沒有想起 subscribeNext:,好吧,我就是在使用好多好多次它之後才慢慢入門的,誰讓 RAC 的大部分教程裡面第一個講的就是它呢!
到了這裡,是不是訂閱者這部分就完了呢?我相信你也注意到了,這裡有幾個不對勁的地方:
a.無法隨時中斷訂閱操作。想想訂閱了一個無限次的定時器信號,無法中斷訂閱操作的話,定時器就是永不停止的發下去。
b.訂閱完成或錯誤時,沒有統一的地方做清理、掃尾等工作。比如現在有一個上傳文件的信號,當上傳完成或上傳錯誤時,你得斷開與文件服務器的網絡連接,還得清空內存裡的文件數據。
4、Disposable
RACDisposable
針對上述兩個問題,RACDisposable 應運而生。也就是說 Disposable 有兩個作用:
a.中斷訂閱某信號
b.訂閱完成後,執行一些收尾任務(清理、回收等等)。
訂閱者與 Disposable 的關系:
a.當 Disposable 有“清理”過,那麼訂閱者就不會再接收到這個被“清理”訂閱源的任何事件。舉例而言,就是訂閱者 subscriberX 訂閱了信號 signalA 和 signalB 兩個信號,其所對應的 Disposable 分別為 disposableA 和 disposableB,也就是說 subscriberX 會同時接收來自 signalA 和 signalB 的信號。當我們手動強制 “清理” disposableA 後,subscriberX 就不會再接收來自 signalA 的任何事件;而來自 signalB 的事件則不受影響。
b.當訂閱者 subscriberX 有接收來自任何一個信號的 “error” 或 “completed” 事件時,則不會再接收任何事件了。
可以這麼說:Disposable 代表發生了訂閱行為
根據 Disposable 的作用和與訂閱者的關系,來總結它所需要提供的接口:
a.包含清理任務的 block ;
b.執行清理任務的方法:- (void)dispose ;
c.一個用來表明是否已經 “清理” 過的布爾變量:BOOL disposed 。
咱們為這個 Disposable 也整了一個類,如下:
// .h file
/**
* @brief 一個 disposable 封裝了用於拆除、清理訂閱的任務工作
*/
@interface NLDisposable : NSObject
/**
* @brief 這個 disposable 是否已經拆除過
*/
@property (atomic, assign, getter = isDisposed, readonly) BOOL disposed;
+ (instancetype)disposableWithBlock:(void (^)(void))block;
/**
* @brief 執行拆除工作。能多次調用這個消息,只有第一次調用有效。
*/
- (void)dispose;
@end
// .m file
@interface NLDisposable () {
/**
* @brief 類型類似於:@property (copy) void (^disposeBlock)(void);
* 在 disposal 要執行的任務邏輯。
* 1、如果沒有初媽值的話,則初始值默認為 `self`
* 2、當已經 disposal 過後,值為 NULL。
*/
void * volatile _disposeBlock;
}
@end
@implementation NLDisposable
#pragma mark Properties
- (BOOL)isDisposed {
return _disposeBlock == NULL;
}
#pragma mark Lifecycle
- (id)init {
self = [super init];
if (self == nil) return nil;
_disposeBlock = (__bridge void *)self;
OSMemoryBarrier();
return self;
}
- (id)initWithBlock:(void (^)(void))block {
NSCParameterAssert(block != nil);
self = [super init];
if (self == nil) return nil;
_disposeBlock = (void *)CFBridgingRetain([block copy]);
OSMemoryBarrier();
return self;
}
+ (instancetype)disposableWithBlock:(void (^)(void))block {
return [[self alloc] initWithBlock:block];
}
- (void)dealloc {
if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return;
CFRelease(_disposeBlock);
_disposeBlock = NULL;
}
#pragma mark Disposal
- (void)dispose {
/**
* @brief 這裡為了讓邏輯更清晰,去掉了原子性操作。具體代碼請看 RACDisposable
*/
if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return;
void (^disposeBlock)(void) = CFBridgingRelease((void *)_disposeBlock);
_disposeBlock = NULL;
disposeBlock();
}
@end從這個類提供的接口來看,顯然是做不到 “訂閱者與 Disposable 的關系” 中的第2條的。因為這條中所描述的是一個訂閱者訂閱多個信號,且能手動中斷訂閱其中一個信號的功能,而 NLDisposable 是單個訂閱關系所設計的。
RACCompoundDisposable
那怎麼組織這“多個”的關系呢?數組?Good,就是數組。OK,咱們來相像一下這個方案的初步代碼。每個訂閱者有一個 Disposable 數組,訂閱一個一個信號,則加入一個 Disposable;當手動拆除一個訂閱關系時,找到與之相關的 Disposable,發送 dispose 消息,將其從數組中移除;當訂閱者不能再接收消息時(接收過 error 或 completed 消息),要 dispose 數組中所有元素,接下來再加入元素時,直接給這個要加入的元素發送 dispose 消息;在多線程環境下,每一次加入或移除或其遍歷時,都得加鎖...(好吧,我編不下去了)
這麼復雜,看來直接用數組來維護是不可行的了。有啥其它可行的法子沒?還好,GoF 對此有個方案,叫做“組合模式”:
組合模式允許你將對象組合成樹形結構來表現 “整體/部分” 層次結構。組合能讓客戶以一致的方式處理個別對象以及對象組合。
使用組合結構,我們能把相同的操作應用在組合和個別對象上。換句話說,在大多數情況下,我們可以 忽略 對象組合和個別對象之間的差別。
本文畢竟不是來講模式的,關於這個模式更多的信息,請自行 google。
RAC 中這個組合類叫 RACCompoundDisposable, 咱們的叫 NLCompoundDisposable,來看看咱們這個類的代碼:
// .h file
#import "NLDisposable.h"
/**
* @brief A disposable of disposables。當它 dispose 時,它會 dispose
* 它所包含的所有的 disposables。
*
* 如果本 compound disposable 已經 dispose 過後,再來調用 -addDisposable:,
* 那麼其參數 disposable 會立馬調用 dispose 方法。
*
* 本類中的方法說明請查看 RACCompoundDisposable 中的同名方法。
* 本類與真正的類 RACCompoundDisposable 代碼差別較大,但本質是一樣的
*/
@interface NLCompoundDisposable : NLDisposable
/**
* @brief 創建並返回一個新的 compound disposable。
*/
+ (instancetype)compoundDisposable;
/**
* @brief 創建並返回一個新的包含了 disposables 的 compound disposable。
*
* @param disposables disposable 數組
*
* @return 一個新的 compound disposable
*/
+ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables;
/**
* @brief 將 disposable 加到本 compound disposable 中。如果本 compound disposable
* 已經 dispose 的話,那麼參數 disposable 會被立即 dispose。
*
* 本方法是線程安全的。
*
* @param disposable 要被加入的 disposable。如果它為 nil 的話,那麼什麼也不會發生。
*/
- (void)addDisposable:(NLDisposable *)disposable;
/**
* @brief 從本 compound disposable 移除指定的 disposable(不管這個 disposable 是什麼
* 狀態);如果這個 disposable 不在本 compound disposable 中,則什麼也不會發生。
*
* 本方法是線程安全的。
*
* @param disposable 要被移除的 disposable。可以為 nil。
*/
- (void)removeDisposable:(NLDisposable *)disposable;
@end
// .m file
#import "NLCompoundDisposable.h"
#import @interface NLCompoundDisposable () {
/**
* @brief 同步鎖
*/
OSSpinLock _spinLock;
/**
* @brief 本 compound disposable 所包含的 disposables。
*
* 在操作這個數組時,應該使用 _spinLock 進行同步。如果
* `_disposed` 為 YES,則這個數組可能為 nil。
*/
NSMutableArray *_disposables;
/**
* @brief 本 compound disposable 是否已經 dispose 。
*
* 在操作這個變量時,應該使用 _spinLock 進行同步。
*/
BOOL _disposed;
}
@end
@implementation NLCompoundDisposable
#pragma mark Properties
- (BOOL)isDisposed {
OSSpinLockLock(&_spinLock);
BOOL disposed = _disposed;
OSSpinLockUnlock(&_spinLock);
return disposed;
}
#pragma mark Lifecycle
+ (instancetype)compoundDisposable {
return [[self alloc] initWithDisposables:nil];
}
+ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables {
return [[self alloc] initWithDisposables:disposables];
}
- (id)initWithDisposables:(NSArray *)otherDisposables {
self = [self init];
if (self == nil) return nil;
if ([otherDisposables count]) {
_disposables = [NSMutableArray arrayWithArray:otherDisposables];
}
return self;
}
- (id)initWithBlock:(void (^)(void))block {
NLDisposable *disposable = [NLDisposable disposableWithBlock:block];
return [self initWithDisposables:@[ disposable ]];
}
- (void)dealloc {
_disposables = nil;
}
#pragma mark Addition and Removal
- (void)addDisposable:(NLDisposable *)disposable {
NSCParameterAssert(disposable != self);
if (disposable == nil || disposable.disposed) return;
BOOL shouldDispose = NO;
OSSpinLockLock(&_spinLock);
{
if (_disposed) {
shouldDispose = YES;
} else {
if (_disposables == nil) {
_disposables = [NSMutableArray array];
}
[_disposables addObject:disposable];
}
}
OSSpinLockUnlock(&_spinLock);
if (shouldDispose) {
[disposable dispose];
}
}
- (void)removeDisposable:(NLDisposable *)disposable {
if (disposable == nil) return;
OSSpinLockLock(&_spinLock);
{
if (!_disposed) {
if (_disposables != nil) {
[_disposables removeObject:disposable];
}
}
}
OSSpinLockUnlock(&_spinLock);
}
#pragma mark RACDisposable
- (void)dispose {
NSArray *remainingDisposables = nil;
OSSpinLockLock(&_spinLock);
{
_disposed = YES;
remainingDisposables = _disposables;
_disposables = nil;
}
OSSpinLockUnlock(&_spinLock);
if (remainingDisposables == nil) return;
[remainingDisposables makeObjectsPerformSelector:@selector(dispose)];
}
@endRACScheduler 簡介
本文不打算研究 RACScheduler 源碼,但其又是 RAC 中不可或缺的一個組件,在研究 RACSignal 的源碼時不可避免地會遇到它,所以對其作下介紹還是有必要的。其實它的源碼並不復雜,可自行研究。
ReactiveCocoa 中 RACSignal 發送的所有事件的傳遞交給了一個特殊的框架組件——調度器,即 RACScheduler 類簇(類簇模式稍後介紹)。調度器是為了簡化 同步/異步/延遲 事件傳遞 以及 取消預定的任務(scheduded actions) 這兩種 RAC 中常見的動作而提出來的。“事件傳遞” 簡單而言就是些 blocks,RACScheduler 所做的就是:調度這些 blocks (schedule blokcs,還是英文的意思准確些)。我們可以通過那些調度方法所返回的 RACDisposable 對象來取消那些 scheduling blocks。
正如前面所說,RACScheduler 是一個類簇。咱們來看看幾種具體的調度器:
a.RACImmediateScheduler
這是 RAC 內部使用的私有調度器,只支持同步 scheduling。就是簡單的馬上執行 block。這個調試器的延遲 scheduling 是通過調用 -[NSThread sleepUntilDate:] 來阻塞當前線程來達到目的的。顯然,這樣一個調度器,沒法取消 scheduling,所以它那些方法返回的 disposables 啥也不會做(實際上,它那些 scheduling 方法返回的是nil)。
b.RACQueueScheduler
這個調度器使用 GCD 隊列來 scheduling blocks。如果你對 GCD 有所了解的話,你會發現這個調度器的功能很簡單,它只是在 GCD 隊列 dispatching blocks 上的簡單封裝罷了。
c.RACSubscriptionScheduler
這是另一個內部使用的私有調度器。如果當前線程有調度器(調度器可以與線程相關聯起來:associated)那它就將 scheduling 轉發給這個線程的調度器;否則就轉發給默認的 background queue 調試器。
接口
調試器有下面一些方法:
- (RACDisposable *)schedule:(void (^)(void))block; - (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block; - (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block; - (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block;
scheduling block 如下:
RACDisposable *disposable = [[RACScheduler mainThreadScheduler] afterDelay:5.0 schedule:^{
// do something
}];
// 如果你想要取消 scheduling block
[disposable dispose]; // block scheduling 被取消了,不會再被執行。5、Subscriber 和 Disposable
前面介紹了 Disposable 的來源,現在來研究下怎麼使用它。還記得嗎,訂閱者與信號打交道的唯一方式是 RACSignal 中的一個方法:
- (RACDisposable *)subscribe:(id )subscriber;
自定義信號所對應的類是 RACDynamicSignal。RACSignal 采用的是類簇模式。除自定義信號之外還有幾種其它的信號,之後會研究到。OC 中的 NSNumber 用的就是類簇模式。類簇是Foundation框架中廣泛使用的設計模式。類簇將一些私有的、具體的子類組合在一個公共的、抽象的超類下面,以這種方法來組織類可以簡化一個面向對象框架的公開架構,而又不減少功能的豐富性。
咱們來研究一下自定義信號裡的這個方法的實現。這個方法實現的難處在於:“一個訂閱者可以訂閱多個信號,並可以手動拆除其中任何一個訂閱”。針對這個問題,提出了上節講到的 RACDisposable。也就是說,在每一次訂閱時,都會返回一個與這次訂閱相關的 Disposable,那怎麼做到這一點呢?
給訂閱者添加一個 CompoundDisposable 類型的屬性 (畢竟 CompoundDisposable 就是用來針對多個 Disposable 的統一管理而存在的),然後在每一次訂閱時,都加一個 Disposable 到這個屬性裡,行不行?但很可惜,訂閱者是一個協議 protocol RACSubscriber,而不是一個具體的類,咱們在使用到它時,都是別人實現了這個協議的類的對象,所以咱們不太可能做到說給這麼一個未知的類添加一個屬性。
事實上,RAC 中確實有 RACSubscriber 這麼一個私有類(它是咱們第一個自定義類 NLSubscriber 的原型),咱們叫它做 class RACSubscriber。嗯,class RACSusbscriber 實現了 protocol RACSubscriber 協議:@interface RACSubscriber : NSObject
咱們可以用裝飾模式來解決這個問題
裝飾模式。在不必改變原類文件和使用繼承的情況下,動態地擴展一個對象的功能。
訂閱者裝飾器 RACPassthroughSubscriber
在訂閱者每一次訂閱信號時產生一個 Disposable,並將其與此次訂閱關聯起來,這是通過裝飾器 RACPassthroughSubscriber 來做到的。這個裝飾器的功能:
a.包裝真正的訂閱者,使自己成為訂閱者的替代者。
b.將真正的訂閱者與一個訂閱時產生的 Disposable 關聯起來。
這正是一個裝飾器所應該做的。依之前的,咱們來模仿這個裝飾器,新建一個咱們的裝飾器:NLPassthroughSubscriber,來看下它的代碼:
// .f file
@class RACCompoundDisposable;
@class RACSignal;
/**
* @brief 這是一個訂閱者的裝飾器。在沒有 dispose 時,它會把接收到的所有
* 的事件都轉發給真實的訂閱者。
*/
@interface NLPassthroughSubscriber : NSObject
/**
* @brief 初始化方法
*
* @param subscriber 被包裝的真實的訂閱者,本裝飾器會把接收到的所有的事件都轉發給這個訂閱者。不能為 nil
* @param signal 要發送事件給這個裝飾器的信號。
* @param disposable 當這個 disposable 接收到 dispose 消息後,將不會再轉發事件。不能為 nil
*
* @return 返回一個初始化後的 passthrough subscriber
*/
- (instancetype)initWithSubscriber:(id)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable;
@end
// .m file
@interface NLPassthroughSubscriber ()
// 被轉發事件的訂閱者。
@property (nonatomic, strong, readonly) idinnerSubscriber;
// 要發送事件給本裝飾器的信號
@property (nonatomic, unsafe_unretained, readonly) RACSignal *signal;
// 代表當前訂閱關系的 disposable。當它 dispose 後,將不會再轉發任何事件給 `innerSubscriber`。
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;
@end
@implementation NLPassthroughSubscriber
#pragma mark Lifecycle
- (instancetype)initWithSubscriber:(id)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
NSCParameterAssert(subscriber != nil);
self = [super init];
if (self == nil) return nil;
_innerSubscriber = subscriber;
_signal = signal;
_disposable = disposable;
/**
* 告訴訂閱者:發生了訂閱行為。並將這次訂閱行為相關的 `Disposable` 傳給訂閱者。
*/
[self.innerSubscriber didSubscribeWithDisposable:self.disposable];
return self;
}
#pragma mark RACSubscriber
- (void)sendNext:(id)value {
/**
* 如果 disposable 已經 dispose 過,就不再轉發事件
*/
if (self.disposable.disposed) return;
/**
* 轉發 next 事件
*/
[self.innerSubscriber sendNext:value];
}
- (void)sendError:(NSError *)error {
if (self.disposable.disposed) return;
[self.innerSubscriber sendError:error];
}
- (void)sendCompleted {
if (self.disposable.disposed) return;
[self.innerSubscriber sendCompleted];
}
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable {
if (disposable != self.disposable) {
[self.disposable addDisposable:disposable];
}
}
@end自定義信號 RACDynamicSignal 的訂閱方法 subscribe
咱們來看看 RACDynamicSignal 是怎麼來使用 RACPassthroughSubscriber 的,這裡就不自己寫代碼了,直接上它的代碼:
- (RACDisposable *)subscribe:(id)subscriber {
NSCParameterAssert(subscriber != nil);
/**
* 本次訂閱相關 disposable。本方法的返回值,起 拆除 本次訂閱的作用。
*/
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
/**
* 訂閱者裝飾器。
*/
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
/**
* _didSubscriber 是在 `+ createSignal` 方法中進入的 block 參數。
* + (RACSignal *)createSignal:(RACDisposable * (^)(idsubscriber))didSubscribe;
* 這個 block 以 subscriber 為參數,返回一個 disposable,即 innerDisposable,而這個 innerDisposable
* 的作用是在 subscriber 不再訂閱本 signal 時,起回收資源的作用。
*/
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}可以看到,訂閱者裝飾器直接偽裝成真正的訂閱器,傳給 didSubscribe 這個 block 使用。在這個 block 中,會有一些事件發送給訂閱者裝飾器,而這個訂閱者裝飾器則根據 disposable 的狀態來來決定是否轉發給真正的訂閱者。disposable 作為返回值,返回給外部,也就是說能夠從外部來取消這個訂閱了。
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];從這幾行代碼中,我們可以看到,didSubscribe 這個 block 是處於 subscriptionScheduler 這個 scheduler 的調度中。RACSubscriptionScheduler 的調度是取決於當前所在的線程的,即 didSubscribe 可能會在不同的調度器中被執行。
假設當前 -(RACDisposable *)subscribe:(id
6、再次改進NLSubscriber
a.didSubscribeWithDisposable
@protocol RACSubscriber @required - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable; @end
這個 RACSubscriber 協議中聲明的一個方法,在最開始的時候被我們特意給忽略,現在是時候回過頭來看看它了。對於一個訂閱者來說,next、error 和 completed 三種事件分別對應協議裡的三種方法,那麼這個方法存在的意義是什麼呢?
從 RACSubscriber 協議中,可以看到,當一個訂閱者有收到過 error 或 completed 事件後,這個訂閱者就不能再接收任何事件了,換句話說,此時這個訂閱者會解除所有的訂閱關系,且無法再次訂閱。既然要解除所有訂閱,首先我得知道我訂閱過哪些信號是不?而代表一個訂閱行為的就是 disposable ,告訴它就傳一個給它好了。所以這個方法就是告訴訂閱者:你發生了訂閱行為。
那為啥要 RACCompoundDisposable 類型作為參數呢?因為有些訂閱者會針對其附加一些操作,而只有這個類型的 disposable 才能動態加入一些操作。接下來我們就會看到的。
b.NLSubscriber 結合 RACDisposable
這一次改進 NLSubscriber 的目的是讓其可以終結自己的訂閱能力的功能。同時實現 didSubscribeWithDisposable 方法。千言萬語不如實際代碼,讓我們來一探究竟:
#import "NLSubscriber.h"
#import @interface NLSubscriber ()
@property (nonatomic, copy) void (^next)(id value);
@property (nonatomic, copy) void (^error)(NSError *error);
@property (nonatomic, copy) void (^completed)(void);
/**
* @brief 代表訂閱者本身總體訂閱行為的 disposable。
* 當有接收到 error 或 completed 事件時,應該 dispose 這個 disposable。
*/
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;
@end
@implementation NLSubscriber
#pragma mark Lifecycle
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
NLSubscriber *subscriber = [[self alloc] init];
subscriber->_next = [next copy];
subscriber->_error = [error copy];
subscriber->_completed = [completed copy];
return subscriber;
}
- (instancetype)init {
self = [super init];
if (self == nil) return nil;
@unsafeify(self);
/**
* 當 _disposable 被發送 dispose 消息時,將 next、error 和 completed 這三個
* block 設置為 nil,從而間實現訂閱者無法再接收任何事件的功能。
*/
RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{
@strongify(self);
@synchronized (self) {
self.next = nil;
self.error = nil;
self.completed = nil;
}
}];
_disposable = [RACCompoundDisposable compoundDisposable];
[_disposable addDisposable:selfDisposable];
return self;
}
- (void)dealloc {
[self.disposable dispose];
}
#pragma mark RACSubscriber
- (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();
}
}
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)otherDisposable {
if (otherDisposable.disposed) return;
/**
* 將 otherDisposable 添加到 selfDisposable 中。這樣當 selfDisposable 被 dispose 時,
* otherDisposable 也能被 dispose。
*
* 這樣,當訂閱者接收到 error 或 completed 事件時,就能解除這個訂閱者自身的所有訂閱行為了。
*/
RACCompoundDisposable *selfDisposable = self.disposable;
[selfDisposable addDisposable:otherDisposable];
@unsafeify(otherDisposable);
/**
* 如果這個訂閱行為被解除,就將 otherDisposable 從 selfDisposable 中移除。
* (我們給 otherDisposable 增加了行為,這也就是參數需要是 RACCompoundDisposable
* 類型的原因了。當然,其它的訂閱者怎麼用這個參數就跟其實際的業務相關了。)
*/
[otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{
@strongify(otherDisposable);
[selfDisposable removeDisposable:otherDisposable];
}]];
}
@endc.改進類別 nl_Subscription
還記得麼?nl_Subscription 類別中的訂閱方法一旦訂閱,就無法停止了,這顯然有很大的問題。解決這個問題很簡單,直接將 disposable 返回即可:
// .h file
@interface RACSignal (nl_Subscription)
...
- (RACDisposable *)nl_subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
@end
// .m file
@implementation RACSignal (nl_Subscription)
...
- (RACDisposable *)nl_subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock {
NLSubscriber *subscriber = [NLSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock];
return [self subscribe:subscriber];
}RACSignal - Operations
本節主要研究這些操作(Operations) —— flattenMap:、map:、 filter: ….
終於看到你想看的東西了?好吧,我承認,上節的東西很無趣,可能壓根不是你想看的東西。但如果沒弄清上面的內容的話,直接研究 Operations 可是會比較吃力的喲~
你以為咱們現在開始研究 Operations?哈哈,你又得失望了~ 咱得先看看這兩個類:RACEmptySignal 和 RACReturnSignal。
1、兩個 RACSignal 的特殊子類 RACEmptySignal 和 RACReturnSignal
a.RACEmptySignal
RACEmptySignal 是 +[RACSignal empty] 的內部實現,一個私有 RACSignal 子類。它就是一個會立即 completed 的信號。讓我們來看看它的 - subscribe: 方法:
- (RACDisposable *)subscribe:(id)subscriber {
NSCParameterAssert(subscriber != nil);
/**
* 只要一訂閱,就給 subscriber 發送 completed 事件。
*/
return [RACScheduler.subscriptionScheduler schedule:^{
[subscriber sendCompleted];
}];
}這樣一個訂閱者一訂閱就會 completed 信號有什麼用呢?稍後揭曉。
b.RACReturnSignal
RACReturnSignal 是 +[RACSignal return:] 的內部實現,也是一個私有 RACSignal 子類。它會同步發送出一個值(即 next)給訂閱者,然後再發送 completed 事件。 它比 RACEmptySignal 多了一點點東西,它是。直接看其實現:
@interface RACReturnSignal ()
// 在本信號被訂閱時會發送的值。
@property (nonatomic, strong, readonly) id value;
@end
@implementation RACReturnSignal
#pragma mark Lifecycle
+ (RACSignal *)return:(id)value {
RACReturnSignal *signal = [[self alloc] init];
signal->_value = value;
return signal;
}
#pragma mark Subscription
- (RACDisposable *)subscribe:(id)subscriber {
NSCParameterAssert(subscriber != nil);
return [RACScheduler.subscriptionScheduler schedule:^{
[subscriber sendNext:self.value];
[subscriber sendCompleted];
}];
}
@end純吐槽:為啥要叫 ReturnSignal 呢?不如直接 OneValueSignal 好了。O(∩_∩)O~~ 不過說真的,RAC 的命名真心不咋地。
那麼發送一個 next 後又 completed 的信號又有啥用呢?等下會知道地。
b.concat: 練手
-[RACSignal concat:] 是源碼較簡單,且使用頻率也較多的。那咱們就來拿它來練練手好了。
- (RACSignal *)concat:(RACSignal *)signal {
return [[RACSignal createSignal:^(id subscriber) {
RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init];
RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
RACDisposable *concattedDisposable = [signal subscribe:subscriber];
serialDisposable.disposable = concattedDisposable;
}];
serialDisposable.disposable = sourceDisposable;
return serialDisposable;
}] setNameWithFormat:@"[%@] -concat: %@", self.name, signal];
}RACSerialDisposable 是 RACDisposable 的子類,它包含一個 Disposable,能夠在運行時設置這個 Disposable。當設置新的 newDisposable時,老的 oldDisposable 會被 dispose。當 RACSerialDisposable 被 dispose 時,其所包含的 Disposable 會被 dispose。
基本上,對一個 RACSignal 的操作的返回值是一個新的 RACSignal 值時,其內部都是調用了 +[RACSignal createSignal:] 這個方法。這個創建信號返回的實際是自定義信號:RACDynamicSignal,針對它前文有所介紹。
這裡有一個小技巧。因為很多信號的操作是針對該信號本身 self 所發送的值作的操作。那也就是說會訂閱 self,那咱們先找到這一句再說:self subscribe: 或 self subscribeNext:...。嗯,找到了這幾行:
RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
RACDisposable *concattedDisposable = [signal subscribe:subscriber];
serialDisposable.disposable = concattedDisposable;
}];在訂閱了 self 後,將 next 和 error 事件發送給訂閱者 subscriber。當 self 發送了 completed 事件事,再讓 subscriber 訂閱參數 signal。也就是當源信號完成後訂閱 signal。怎麼樣,很簡單吧。
c.zipWith:
再來一個練手的玩意。-[RACSignal zipWith:] 比 -[RACSignal concat:] 稍微復雜點。它是將 self 和 參數 signal 兩個信號發送的值合並起來發送給訂閱者。
- (RACSignal *)zipWith:(RACSignal *)signal {
NSCParameterAssert(signal != nil);
return [[RACSignal createSignal:^(id subscriber) {
__block BOOL selfCompleted = NO;
NSMutableArray *selfValues = [NSMutableArray array];
__block BOOL otherCompleted = NO;
NSMutableArray *otherValues = [NSMutableArray array];
void (^sendCompletedIfNecessary)(void) = ^{
@synchronized (selfValues) {
BOOL selfEmpty = (selfCompleted && selfValues.count == 0);
BOOL otherEmpty = (otherCompleted && otherValues.count == 0);
if (selfEmpty || otherEmpty) [subscriber sendCompleted];
}
};
void (^sendNext)(void) = ^{
@synchronized (selfValues) {
if (selfValues.count == 0) return;
if (otherValues.count == 0) return;
RACTuple *tuple = [RACTuple tupleWithObjects:selfValues[0], otherValues[0], nil];
[selfValues removeObjectAtIndex:0];
[otherValues removeObjectAtIndex:0];
[subscriber sendNext:tuple];
sendCompletedIfNecessary();
}
};
RACDisposable *otherDisposable = [signal subscribeNext:^(id x) {
@synchronized (selfValues) {
[otherValues addObject:x ?: RACTupleNil.tupleNil];
sendNext();
}
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
@synchronized (selfValues) {
otherCompleted = YES;
sendCompletedIfNecessary();
}
}];
return [RACDisposable disposableWithBlock:^{
[selfDisposable dispose];
[otherDisposable dispose];
}];
}] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal];
}同樣的,重點在 [self subscriberNext:] 和 [signal subscribeNext:] 處。這裡的實現是訂閱 self 和 signal 信號,然後將它們發送出的值收集起來,當兩個都發出了值時,分別拿出兩個信號最早發出的值,合並為一個 RACTuple,再發送給訂閱者 subscriber。這個也很簡單吧,只是代碼稍多點而已。
d.bind:
(i)說明
信號的很多 operations 的實現調用來調用去最後都是調用了這個 -[RACSignal bind:] 方法,比如 flattenMap: 、map:、filter 等等。那咱們就來看看這個方法是哪路神仙?
這是在 RACStream 中聲明的抽象方法。來看看它的聲明:
typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop); - (instancetype)bind:(RACStreamBindBlock (^)(void))block;
RACStreamBindBlock 是一個 block。它從一個 RACStream 中接收一個值,並且返回一個與該流相同類型的實例。如果將 stop 設為 YES,則會在返回一個實例後終結此次 bind。如果返回 nil 則會立即終結。
bind: 方法是將流中每一個值都放到 RACStreamBindBlock 中跑一下。來看看其參數:block。然而這有什麼卵用呢?好吧,我太笨,從它的說明來看,我真的不能理解它有什麼用。
(ii)源碼解讀
既然從方法說明了解不到,那直接來看其源碼了。
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
return [[RACSignal createSignal:^(id subscriber) {
RACStreamBindBlock bindingBlock = 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];
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];
// 一.
RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
// Manually check disposal to handle synchronous errors.
if (compoundDisposable.disposed) return;
BOOL stop = NO;
id signal = bindingBlock(x, &stop);
@autoreleasepool {
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];
}我們一步一步來看。先從第 一 步開始,其步驟如下:
訂閱 self
針對 self 發出的每一個值 x,經過 bindingBlock,獲取一個信號:signal
1. 如果 signal 不為 nil,就轉到第二步:addSignal
2. 如果 signal 為 nil,或 stop 為 YES,則轉到第三步:completedSignal
3. 如果 self 發出 error 事件,則中斷訂閱;如果 self 發出 completed 事件則轉到第三步:completedSignal
第二步:addSignal:signal
先將 signal 添加到 signals中
訂閱 signal
1. 將 signal 的 next 事件轉發給訂閱者 subscriber
2. 如果 signal 發送 error 事件則中斷訂閱
3. 如果 signal 發送 complete 事件,則轉到第三步
第三步:completeSignal:signal:disposable
將 signal 從 signals 中移除
如果 signals 中沒有了 signal,那麼訂閱就完成了
好了,來總結一下這個 -bind: :
訂閱原信號 self 的 values。
將 self 發出的任何一個值,都對其使用 bindingBlock 進行轉換。
如果 bindingBlock 返回一個信號,則訂閱它,將從它那接收到的每個值都傳遞給訂閱者 subscriber。
如果 bindingBlock 要求結束綁定,則 complete self 信號。
如果 所有 的信號全都 complete,則給 subscriber 發送 completed 事件.
如果任何一個信號發出 error,將其發送給 subscriber。
那從中可以玩出什麼花樣呢?
(iii)示例
咱們先用用它,再看看能怎麼玩吧。
示例1:結合 RACReturnSignal
RACSignal *signalInterval = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] take:3];
RACSignal *bindSignal = [signalInterval bind:^RACStreamBindBlock{
return ^(id value, BOOL *stop) {
NSLog(@"inner value: %@", value);
return [RACSignal return:value];
};
}];
[bindSignal subscribeNext:^(id x) {
NSLog(@"outer value: %@", x);
}];輸出如下:
2015-08-27 17:16:17.933 RACPraiseDemo[3063:168556] inner value: 2015-08-27 09:16:17 +0000 2015-08-27 17:16:17.934 RACPraiseDemo[3063:168556] outer value: 2015-08-27 09:16:17 +0000 2015-08-27 17:16:18.931 RACPraiseDemo[3063:168556] inner value: 2015-08-27 09:16:18 +0000 2015-08-27 17:16:18.931 RACPraiseDemo[3063:168556] outer value: 2015-08-27 09:16:18 +0000 2015-08-27 17:16:19.931 RACPraiseDemo[3063:168556] inner value: 2015-08-27 09:16:19 +0000 2015-08-27 17:16:19.931 RACPraiseDemo[3063:168556] outer value: 2015-08-27 09:16:19 +0000
這個示例就是在 bind: 中簡單的返回值。那咱們將這個值變化一下如何?
示例2:結合 RACReturnSignal、轉換 value
RACSignal *signalInterval = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] take:3];
RACSignal *bindSignal = [signalInterval bind:^RACStreamBindBlock{
return ^(NSDate *value, BOOL *stop) {
NSLog(@"inner value: %@", value);
NSTimeInterval nowTime = [value timeIntervalSince1970];
return [RACSignal return:@(nowTime)];
};
}];
[bindSignal subscribeNext:^(id x) {
NSLog(@"outer value: %@", x);
}];輸出如下:
2015-08-27 17:34:04.938 RACPraiseDemo[3153:176383] inner value: 2015-08-27 09:34:04 +0000 2015-08-27 17:34:04.939 RACPraiseDemo[3153:176383] outer value: 1440668044.936496 2015-08-27 17:34:05.939 RACPraiseDemo[3153:176383] inner value: 2015-08-27 09:34:05 +0000 2015-08-27 17:34:05.939 RACPraiseDemo[3153:176383] outer value: 1440668045.939163 2015-08-27 17:34:06.941 RACPraiseDemo[3153:176383] inner value: 2015-08-27 09:34:06 +0000 2015-08-27 17:34:06.941 RACPraiseDemo[3153:176383] outer value: 1440668046.941275
哇哇,這就是個 map: 有木有? 現在,有感受到 RACReturnSignal 的魅力?RACReturnSignal 和 -bind: 結合能轉換 value。
示例3:結合 RACEmptySignal
現在來換個玩法試試看,這回換 RACEmptySignal 來玩玩。
RACSignal *signalInterval = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] take:3];
__block NSUInteger count = 0;
RACSignal *bindSignal = [signalInterval bind:^RACStreamBindBlock{
return ^(NSDate *value, BOOL *stop) {
NSLog(@"inner value: %@", value);
++count;
if (count % 2 == 0) {
return [RACSignal empty];
}
return [RACSignal return:value];
};
}];
[bindSignal subscribeNext:^(id x) {
NSLog(@"outer value: %@", x);
}];輸出如下:
2015-08-27 17:53:45.345 RACPraiseDemo[3363:188270] inner value: 2015-08-27 09:53:45 +0000 2015-08-27 17:53:45.346 RACPraiseDemo[3363:188270] outer value: 2015-08-27 09:53:45 +0000 2015-08-27 17:53:46.345 RACPraiseDemo[3363:188270] inner value: 2015-08-27 09:53:46 +0000 2015-08-27 17:53:47.342 RACPraiseDemo[3363:188270] inner value: 2015-08-27 09:53:47 +0000 2015-08-27 17:53:47.342 RACPraiseDemo[3363:188270] outer value: 2015-08-27 09:53:47 +0000
這一次,“outer value” 比 “inner value” 少了一個,這就是 filter: 呀!RACEmptySignal 與 bind: 結合能過濾 value。
示例4:改進 bind:
經過這幾個示例,我們可以發現,直接使用 bind: 是比較麻煩的。而一般情況下,咱們還真用不到 stop,那咱們就改進一下呗:
- (instancetype)flattenMap:(RACStream * (^)(id value))block {
Class class = self.class;
return [[self bind:^{
return ^(id value, BOOL *stop) {
/**
* 如果 block 返回 nil,得用 RACEmptySignal 代替,
* 不然會結束 `bind:`
*/
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];
}哈哈,這個就是 - flattenMap:了。不必過多解釋了吧~
(iv)-map:
嗯,這其實就是 -flattenMap: 與 RACReturnSignal 的結合:
- (instancetype)map:(id (^)(id value))block {
NSCParameterAssert(block != nil);
Class class = self.class;
return [[self flattenMap:^(id value) {
return [class return:block(value)];
}] setNameWithFormat:@"[%@] -map:", self.name];
}(v)-flatten
信號可以發送類型的值,當然也包括 RACSignal 類型。例如,RACCommand 的 executionSignals 這個信號,它發出的值就是 RACSignal 類型的。對於這種發出的值是 RACSignal 類型的 RACSignal,叫做 signal of signals。這有點類似於 disposable of disposables。
既然這個信號發出的就是 RACSignal,那在 -flattenMap:中,我們直接將 value 返回就好了。來看看示例:
/**
* 有效三次的間隔為1秒定時器信號,此時 signalInterval 是一個 signal of NSDates
*/
RACSignal *signalInterval = [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] take:3];
/**
* 將定時器信號裡的值修改成 RACSignal 類型
* 此時,signalInterval 變成了一個 signal of signals
*/
signalInterval = [signalInterval map:^id(NSDate *date) {
return [RACSignal return:date];
}];
/**
* 既然 signalInterval 裡的值都是信號,那直接將這些信號返回即可
*/
RACSignal *signal = [signalInterval flattenMap:^RACStream *(RACSignal *returnSignal) {
return returnSignal;
}];
/**
* 由於 signalInterval 裡的值都是包含了一個 NSDate 值的 RACReturnSignal,
* 經過 `-flattenMap:` 過後,signal 就變成了 signal of NSDates。
*/
[signal subscribeNext:^(id x) {
NSLog(@"value: %@", x);
}];輸出如下:
2015-08-27 21:16:29.517 RACPraiseDemo[549:11996] value: 2015-08-27 13:16:29 +0000 2015-08-27 21:16:30.516 RACPraiseDemo[549:11996] value: 2015-08-27 13:16:30 +0000 2015-08-27 21:16:31.516 RACPraiseDemo[549:11996] value: 2015-08-27 13:16:31 +0000
(vi)小結
RACSignal 的 operations 實在太多,全部在這裡列出來不現實,也沒有這個必要。我相信,經過前面的解析,你現在再去看其它 的一個 operation 源碼,也應該不是太大的難事。
RAC() 宏展開
RAC 的最大的魅力之一就是綁定:RAC(self, ...) = signal; 這應該是大家經常寫的一條語句。有沒有想過它是怎麼工作的呢?咱們來看點代碼:
@property (nonatomic, strong) NSString *text;
//--------
RACSignal *signal = [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] take:3];
signal = [signal map:^id(id value) {
return [value description];
}];
RAC(self, text) = signal;重點在 RAC(self, text) = signal; 這一行。先來看看將這個宏展開是什麼樣子(RAC 對宏的運用很是牛B,有興趣請看這篇文章):
[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self) nilValue:nil][@"text"] = signal;
看得更清楚一點:
RACSubscriptingAssignmentTrampoline *assignment = [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(self) nilValue:nil]; assignment[@"text"] = signal;
跳到 RACSubscriptingAssignmentTrampoline 類的聲明,可以看到:
@interface RACSubscriptingAssignmentTrampoline : NSObject - (id)initWithTarget:(id)target nilValue:(id)nilValue; // 這是可以使用下標 `[]` 語法的關鍵 - (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath; @end
這個類使用了 clang 的特性,可以使用 []語法([] 的相關文章)。也就是說 assignment[@"text"] = signal;,實際上是這樣子的:
[assignment setObject:signal forKeyedSubscript:@"text"];
再看 - (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath; 這個方法的實現,我們發現,它其實調用的是 signal 的方法:-
- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue {
...
RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
...
[object setValue:x ?: nilValue forKeyPath:keyPath];
}...
...
}哦,原來它就是訂閱了 signal,並將 signal 發出的每一值都設置給 object 的 keyPath 屬性而已。很簡單嘛~
結束
本文研究了 RAC 中的一些基本組件,並沒有對一些高級內容進行深入研究,所以才叫“淺析”。但這些也是對高級內容深入研究的基礎,既然有“漁”,何懼無“魚”呢?
其實頗想繼續分享,但心有余而力不足。
還可研究的主題:
Subjects 它也是 RACSignal 一些操作的基礎,值得研究。難度系數:2 (最高為5)
RACMulticastConnection 常用,值得研究。難度系數:3
Foundation、UIKit、KVO (給各系統類加的 rac_ 擴展),有研究價值。研究過後,你會對 runtime 會有很深入的了解,還會接觸到一些 OC 中少用的知識(如 NSProxy 等),能開拓視野。難度系數:5
難度系數是本人 YY 出來的,別較真,僅當參考。