RAC的Bind方法
RAC提供了一堆可以提高開發效率的方法,比如filter
,map
,flattenMap
等值處理方法,幾乎每個方法點到底,都能看到一個叫做bind
的方法.這個方法就是RAC相對底層的方法.弄明白它,對于理解RAC是非常有幫助的.
現象
- (void)runTest {
// 1.
RACSubject *subject = [RACSubject subject];
// 2.
RACSignal *bindSignal = [subject bind:^RACSignalBindBlock _Nonnull {
return ^RACSignal *( id value, BOOL *stop ) {//這個block只要源信號發送數據,就會調用
NSLog(@"接收到源信號的內容: %@",value);
//2.1
//返回信號,不能傳nil,如果真的想返回空信號,用empty(其實就是一個alloc
// init) . 如果返回empty,那就訂閱鏈就斷了.
// return [RACSignal empty];
return [RACReturnSignal return:value];
};
}];
// 3.
[bindSignal subscribeNext:^( id _Nullable x ){
//3.1
NSLog(@"訂閱的地方,也就是處理完的信號:%@",x);
}];
// 4.
[subject sendNext:@"123"];
}
1.創建源信號
2.通過bind得到綁定信號
任何信號都能調用 bind 方法,bind方法需要一個RACSignalBindBlock
類型的參數,這個類型定義typedef RACSignal * _Nullable (^RACSignalBindBlock)(id _Nullable value, BOOL *stop)
, 早期版本,返回值是 RACStream
,現在是 RACSignal
,其實都一樣.RACSignal
繼承RACStream
. 響應式編程中,萬物皆是流
3.訂閱綁定信號
3.1 如果2.1處返回的是 empty
, 那么3.1處將不會執行.
4.源信號發送數據
從源碼看
- 創建信號
RACSubject *subject = [RACSubject subject];
RACSubject.m文件中
- (instancetype)init {
self = [super init];
if (self == nil) return nil;
_disposable = [RACCompoundDisposable compoundDisposable];
_subscribers = [[NSMutableArray alloc] initWithCapacity:1];
return self;
}
_subscribers
這個是訂閱者數組.
- 綁定信號
[subject bind:^RACSignalBindBlock _Nonnull{....}
這個bind點進去,里面代碼如下:
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
//沒有 block() ,即沒有執行參數block
//2.1
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
//2.2
RACSignalBindBlock bindingBlock = block();
...
可以看到,在bind方法中,參數block
到結束的return都沒有去調用.bind返回了一個新創建的信號.這個在代碼中,體現為RACSignal *bindSignal
.
而在 return后面的 [[RACSignal createSignal:^(id<RACSubscriber> subscriber)
,這個createSignal
定義如下:
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
可以看出,這個 createSignal:
需要一個didSubscribe
block,也就是在2.1處,傳遞的:
^(id<RACSubscriber> subscriber) {
RACSignalBindBlock bindingBlock = block();
...
繼續看下去,[RACDynamicSignal createSignal:didSubscribe];
這句中,createSignal
的實現如下:
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
就是說,創建了一個 RACDynamicSignal
類型的對象,然后將didSubscribe
block賦值給它的_didSubscribe
屬性. 屬性定義如下:
@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber);
到此,這個bind流程走完了.
總結就是:
bind方法傳遞 RACSignalBindBlock
類型的參數到了 createSignal
的參數block中, createSingal 的參數block被保存為 RACDynamicSignal 的didSubscribe屬性.
注意,到此為止,沒有任何block獲得執行.也就是如果代碼單純如下:
// 1.
RACSubject *subject = [RACSubject subject];
// 2.
RACSignal *bindSignal = [subject bind:^RACSignalBindBlock _Nonnull {
//breakpoint_1
return ^RACSignal *( id value, BOOL *stop ) {
NSLog(@"接收到源信號的內容: %@",value);
//breakpoint_2
return [RACReturnSignal return:value];
};
}];
打在兩個block中的兩個breakpoint都不會執行.
- 訂閱綁定信號
[bindSignal subscribeNext:^( id _Nullable x ){
NSLog(@"訂閱的地方,也就是處理完的信號:%@",x);
}];
訂閱信號,這個subscribeNext
方法如下:
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
//3.1
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
//3.2
return [self subscribe:o];
}
可以看到,方法內部創建了一個RACSubscriber
訂閱者.這個創建方法subscriberWithNext
定義如下:
+ (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;
}
可以看到,就是將對應的 next ,error,complete三種類型的block保存到訂閱者中.在這里,我們的nextBlock就是:
^( id _Nullable x ){
NSLog(@"訂閱的地方,也就是處理完的信號:%@",x);
}];
也就是說,將我們的以上block保存到訂閱者中.
在3.1之后, 3.2 [self subscribe:o]
這是真正的去訂閱信號.訂閱信號,會觸發信號的didSubscribe
.也就是代碼2.2所在的block會獲得執行,而根據剛剛的線路.代碼2.2中有RACSignalBindBlock bindingBlock = block();
,也就是我們傳遞的返回值是RACSignalBindBlock
類型的block會執行.也就是 breakPoint_1會執行 ,而代碼中 block()
的返回值是RACSignalBindBlock
,代碼將其保存到了bindingBlock
這個變量中.
在bind方法末尾:
@autoreleasepool {
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
//3.3
RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
// Manually check disposal to handle synchronous errors.
if (compoundDisposable.disposed) return;
BOOL stop = NO;
//3.4
id signal = bindingBlock(x, &stop);
@autoreleasepool {
if (signal != nil) addSignal(signal);
if (signal == nil || stop) {
[selfDisposable dispose];
completeSignal(selfDisposable);
}
}
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(selfDisposable);
}
}];
selfDisposable.disposable = bindingDisposable;
}
```
3.3 `RACDisposable *bindingDisposable = [self subscribeNext:^(id x) ` self 代指的調用者,也就是我們的源信號.訂閱這個信號,也是會創建一個訂閱者.也是用來保存block的.以上代碼中,nextBlock , errorBlock, completeBlock都有,都會被保存.
而nextBlock做了:
```objc
id signal = bindingBlock(x, &stop);
@autoreleasepool {
//4.5
if (signal != nil) addSignal(signal);
if (signal == nil || stop) {
[selfDisposable dispose];
completeSignal(selfDisposable);
}
}
這樣訂閱流程就走完了.
4.源信號發送數據 [subject sendNext:@"123"];
這樣會執行源信號訂閱者的nextBlock,而nextBlock中有 bindingBlock的執行.bindingBlock有個x參數,這就是我們send的 @"123".
注意4.5這里有個判空的過程:
if (signal != nil) addSignal(signal);
如果在2.2我們返回了空信號,那么addSignal(signal)
是不會得到執行的.
然后4.5這個bingBlock的返回值作為參數,傳遞到了addSignal
中,addSignal
的定義如下:
void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
OSAtomicIncrement32Barrier(&signalCount);
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(selfDisposable);
}
}];
selfDisposable.disposable = disposable;
};
內部,訂閱了我們的處理后的信號(我們開始代碼的bindSignal).這樣新信號的didSubscribe block 就會得到執行.
這就是使用bind進行了一次信號的完整流程.