前言
在上篇文章中,詳細分析了RACSignal是創建和訂閱的詳細過程??吹降讓釉创a實現后,就能發現,ReactiveCocoa這個FRP的庫,實現響應式(RP)是用Block閉包來實現的,而并不是用KVC / KVO實現的。
在ReactiveCocoa整個庫中,RACSignal占據著比較重要的位置,而RACSignal的變換操作更是整個RACStream流操作核心之一。在上篇文章中也詳細分析了bind操作的實現。RACsignal很多變換操作都是基于bind操作來實現的。在開始本篇底層實現分析之前,先簡單回顧一下上篇文章中分析的bind函數,這是這篇文章分析的基礎。
bind函數可以簡單的縮寫成下面這樣子。
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block;
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
RACStreamBindBlock bindBlock = block();
[self subscribeNext:^(id x) { //(1)
BOOL stop = NO;
RACSignal *signal = (RACSignal *)bindBlock(x, &stop); //(2)
if (signal == nil || stop) {
[subscriber sendCompleted];
} else {
[signal subscribeNext:^(id x) {
[subscriber sendNext:x]; //(3)
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
}];
}
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
return nil;
}];
}
當bind變換之后的信號被訂閱,就開始執行bind函數中return的block閉包。
- 在bind閉包中,先訂閱原先的信號A。
- 在訂閱原信號A的didSubscribe閉包中進行信號變換,變換中用到的block閉包是外部傳遞進來的,也就是bind函數的入參。變換結束,得到新的信號B
- 訂閱新的信號B,拿到bind變化之后的信號的訂閱者subscriber,對其發送新的信號值。
簡要的過程如上圖,bind函數中進行了2次訂閱的操作,第一次訂閱是為了拿到signalA的值,第二次訂閱是為了把signalB的新值發給bind變換之后得到的signalB的訂閱者。
回顧完bind底層實現之后,就可以開始繼續本篇文章的分析了。
目錄
- 1.變換操作
- 2.時間操作
一.變換操作
我們都知道RACSignal是繼承自RACStream的,而在底層的RACStream上也定義了一些基本的信號變換的操作,所以這些操作在RACSignal上同樣適用。如果在RACsignal中沒有重寫這些方法,那么調用這些操作,實際是調用的父類RACStream的操作。下面分析的時候,會把實際調用父類RACStream的操作的地方都標注出來。
1.Map: (在父類RACStream中定義的)
map操作一般是用來信號變換的。
RACSignal *signalB = [signalA map:^id(NSNumber *value) {
return @([value intValue] * 10);
}];
來看看底層是如何實現的。
- (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];
}
這里實現代碼比較嚴謹,先判斷self的類型。因為RACStream的子類中會有一些子類會重寫這些方法,所以需要判斷self的類型,在回調中可以回調到原類型的方法中去。
由于本篇文章中我們都分析RACSignal的操作,所以這里的self.class是RACDynamicSignal類型的。相應的在return返回值中也返回class,即RACDynamicSignal類型的信號。
從map實現代碼上來看,map實現是用了flattenMap函數來實現的。把map的入參閉包,放到了flattenMap的返回值中。
在來看看flattenMap的實現:
- (instancetype)flattenMap:(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];
}
flattenMap算是對bind函數的一種封裝。bind函數的入參是一個RACStreamBindBlock類型的閉包。而flattenMap函數的入參是一個value,返回值RACStream類型的閉包。
在flattenMap中,返回block(value)的信號,如果信號為nil,則返回[class empty]。
先來看看為空的情況。當block(value)為空,返回[RACEmptySignal empty],empty就是創建了一個RACEmptySignal類型的信號:
+ (RACSignal *)empty {
#ifdef DEBUG
// Create multiple instances of this class in DEBUG so users can set custom
// names on each.
return [[[self alloc] init] setNameWithFormat:@"+empty"];
#else
static id singleton;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
singleton = [[self alloc] init];
});
return singleton;
#endif
}
RACEmptySignal類型的信號又是什么呢?
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
return [RACScheduler.subscriptionScheduler schedule:^{
[subscriber sendCompleted];
}];
}
RACEmptySignal是RACSignal的子類,一旦訂閱它,它就會同步的發送completed完成信號給訂閱者。
所以flattenMap返回一個信號,如果信號不存在,就返回一個completed完成信號給訂閱者。
再來看看flattenMap返回的信號是怎么變換的。
block(value)會把原信號發送過來的value傳遞給flattenMap的入參。flattenMap的入參是一個閉包,閉包的參數也是value的:
^(id value) { return [class return:block(value)]; }
這個閉包返回一個信號,信號類型和原信號的類型一樣,即RACDynamicSignal類型的,值是block(value)。這里的閉包是外面map傳進來的:
^id(NSNumber *value) { return @([value intValue] * 10); }
在這個閉包中把原信號的value值傳進去進行變換,變換結束之后,包裝成和原信號相同類型的信號,返回。返回的信號作為bind函數的閉包的返回值。這樣訂閱新的map之后的信號就會拿到變換之后的值。
2.MapReplace: (在父類RACStream中定義的)
一般用法如下:
RACSignal *signalB = [signalA mapReplace:@"A"];
效果是不管A信號發送什么值,都替換成@“A”。
- (instancetype)mapReplace:(id)object {
return [[self map:^(id _) {
return object;
}] setNameWithFormat:@"[%@] -mapReplace: %@", self.name, [object rac_description]];
}
看底層源碼就知道,它并不去關心原信號發送的是什么值,原信號發送什么值,都返回入參object的值。
3.reduceEach: (在父類RACStream中定義的)
reduce是減少,聚合在一起的意思,reduceEach就是每個信號內部都聚合在一起。
RACSignal *signalB = [signalA reduceEach:^id(NSNumber *num1 , NSNumber *num2){
return @([num1 intValue] + [num2 intValue]);
}];
reduceEach后面必須傳入一個元組RACTuple類型,否則會報錯。
- (instancetype)reduceEach:(id (^)())reduceBlock {
NSCParameterAssert(reduceBlock != nil);
__weak RACStream *stream __attribute__((unused)) = self;
return [[self map:^(RACTuple *t) {
NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t);
return [RACBlockTrampoline invokeBlock:reduceBlock withArguments:t];
}] setNameWithFormat:@"[%@] -reduceEach:", self.name];
}
這里有兩個斷言,一個是判斷傳入的reduceBlock閉包是否為空,另一個斷言是判斷閉包的入參是否是RACTuple類型的。
@interface RACBlockTrampoline : NSObject
@property (nonatomic, readonly, copy) id block;
+ (id)invokeBlock:(id)block withArguments:(RACTuple *)arguments;
@end
RACBlockTrampoline就是一個保存了一個block閉包的對象,它會根據傳進來的參數,動態的構造一個NSInvocation,并執行。
reduceEach把入參reduceBlock作為RACBlockTrampoline的入參invokeBlock傳進去,以及每個RACTuple也傳到RACBlockTrampoline中。
- (id)invokeWithArguments:(RACTuple *)arguments {
SEL selector = [self selectorForArgumentCount:arguments.count];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]];
invocation.selector = selector;
invocation.target = self;
for (NSUInteger i = 0; i < arguments.count; i++) {
id arg = arguments[i];
NSInteger argIndex = (NSInteger)(i + 2);
[invocation setArgument:&arg atIndex:argIndex];
}
[invocation invoke];
__unsafe_unretained id returnVal;
[invocation getReturnValue:&returnVal];
return returnVal;
}
第一步就是先計算入參一個元組RACTuple里面元素的個數。
- (SEL)selectorForArgumentCount:(NSUInteger)count {
NSCParameterAssert(count > 0);
switch (count) {
case 0: return NULL;
case 1: return @selector(performWith:);
case 2: return @selector(performWith::);
case 3: return @selector(performWith:::);
case 4: return @selector(performWith::::);
case 5: return @selector(performWith:::::);
case 6: return @selector(performWith::::::);
case 7: return @selector(performWith:::::::);
case 8: return @selector(performWith::::::::);
case 9: return @selector(performWith:::::::::);
case 10: return @selector(performWith::::::::::);
case 11: return @selector(performWith:::::::::::);
case 12: return @selector(performWith::::::::::::);
case 13: return @selector(performWith:::::::::::::);
case 14: return @selector(performWith::::::::::::::);
case 15: return @selector(performWith:::::::::::::::);
}
NSCAssert(NO, @"The argument count is too damn high! Only blocks of up to 15 arguments are currently supported.");
return NULL;
}
可以看到最多支持元組內元素的個數是15個。
這里我們假設以元組里面有2個元素為例。
- (id)performWith:(id)obj1 :(id)obj2 {
id (^block)(id, id) = self.block;
return block(obj1, obj2);
}
對應的Type Encoding如下:
argument | return value | 0 | 1 | 2 | 3 |
---|---|---|---|---|---|
methodSignature | @ | @ | : | @ | @ |
argument0和argument1分別對應著隱藏參數self和_cmd,所以對應著的類型是@和:,從argument2開始,就是入參的Type Encoding了。
所以在構造invocation的參數的時候,argIndex是要偏移2個位置的。即從(i + 2)開始設置參數。
當動態構造了一個invocation方法之后,[invocation invoke]調用這個動態方法,也就是執行了外部的reduceBlock閉包,閉包里面是我們想要信號變換的規則。
閉包執行結束得到returnVal返回值。這個返回值就是整個RACBlockTrampoline的返回值了。這個返回值也作為了map閉包里面的返回值。
接下去的操作就完全轉換成了map的操作了。上面已經分析過map操作了,這里就不贅述了。
4. reduceApply
舉個例子:
RACSignal *signalA = [RACSignal createSignal:
^RACDisposable *(id<RACSubscriber> subscriber)
{
id block = ^id(NSNumber *first,NSNumber *second,NSNumber *third) {
return @(first.integerValue + second.integerValue * third.integerValue);
};
[subscriber sendNext:RACTuplePack(block,@2 , @3 , @8)];
[subscriber sendNext:RACTuplePack((id)(^id(NSNumber *x){return @(x.intValue * 10);}),@9,@10,@30)];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"signal dispose");
}];
}];
RACSignal *signalB = [signalA reduceApply];
使用reduceApply的條件也是需要信號里面的值是元組RACTuple。不過這里和reduceEach不同的是,原信號中每個元祖RACTuple的第0位必須要為一個閉包,后面n位為這個閉包的入參,第0位的閉包有幾個參數,后面就需要跟幾個參數。
如上述例子中,第一個元組第0位的閉包有3個參數,所以第一個元組后面還要跟3個參數。第二個元組的第0位的閉包只有一個參數,所以后面只需要跟一個參數。
當然后面可以跟更多的參數,如第二個元組,閉包后面跟了3個參數,但是只有第一個參數是有效值,后面那2個參數是無效不起作用的。唯一需要注意的就是后面跟的參數個數一定不能少于第0位閉包入參的個數,否則就會報錯。
上面例子輸出
26 // 26 = 2 + 3 * 8;
90 // 90 = 9 * 10;
看看底層實現:
- (RACSignal *)reduceApply {
return [[self map:^(RACTuple *tuple) {
NSCAssert([tuple isKindOfClass:RACTuple.class], @"-reduceApply must only be used on a signal of RACTuples. Instead, received: %@", tuple);
NSCAssert(tuple.count > 1, @"-reduceApply must only be used on a signal of RACTuples, with at least a block in tuple[0] and its first argument in tuple[1]");
// We can't use -array, because we need to preserve RACTupleNil
NSMutableArray *tupleArray = [NSMutableArray arrayWithCapacity:tuple.count];
for (id val in tuple) {
[tupleArray addObject:val];
}
RACTuple *arguments = [RACTuple tupleWithObjectsFromArray:[tupleArray subarrayWithRange:NSMakeRange(1, tupleArray.count - 1)]];
return [RACBlockTrampoline invokeBlock:tuple[0] withArguments:arguments];
}] setNameWithFormat:@"[%@] -reduceApply", self.name];
}
這里也有2個斷言,第一個是確保傳入的參數是RACTuple類型,第二個斷言是確保元組RACTuple里面的元素各種至少是2個。因為下面取參數是直接從1號位開始取的。
reduceApply做的事情和reduceEach基本是等效的,只不過變換規則的block閉包一個是外部傳進去的,一個是直接打包在每個信號元組RACTuple中第0位中。
5. materialize
這個方法會把信號包裝成RACEvent類型。
- (RACSignal *)materialize {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
return [self subscribeNext:^(id x) {
[subscriber sendNext:[RACEvent eventWithValue:x]];
} error:^(NSError *error) {
[subscriber sendNext:[RACEvent eventWithError:error]];
[subscriber sendCompleted];
} completed:^{
[subscriber sendNext:RACEvent.completedEvent];
[subscriber sendCompleted];
}];
}] setNameWithFormat:@"[%@] -materialize", self.name];
}
sendNext會包裝成[RACEvent eventWithValue:x],error會包裝成[RACEvent eventWithError:error],completed會被包裝成RACEvent.completedEvent。注意,當原信號error和completed,新信號都會發送sendCompleted。
6. dematerialize
這個操作是materialize的逆向操作。它會把包裝成RACEvent信號重新還原為正常的值信號。
- (RACSignal *)dematerialize {
return [[self bind:^{
return ^(RACEvent *event, BOOL *stop) {
switch (event.eventType) {
case RACEventTypeCompleted:
*stop = YES;
return [RACSignal empty];
case RACEventTypeError:
*stop = YES;
return [RACSignal error:event.error];
case RACEventTypeNext:
return [RACSignal return:event.value];
}
};
}] setNameWithFormat:@"[%@] -dematerialize", self.name];
}
這里的實現也用到了bind函數,它會把原信號進行一個變換。新的信號會根據event.eventType進行轉換。RACEventTypeCompleted被轉換成[RACSignal empty],RACEventTypeError被轉換成[RACSignal error:event.error],RACEventTypeNext被轉換成[RACSignal return:event.value]。
7. not
- (RACSignal *)not {
return [[self map:^(NSNumber *value) {
NSCAssert([value isKindOfClass:NSNumber.class], @"-not must only be used on a signal of NSNumbers. Instead, got: %@", value);
return @(!value.boolValue);
}] setNameWithFormat:@"[%@] -not", self.name];
}
not操作需要傳入的值都是NSNumber類型的。不是NSNumber類型會報錯。not操作會把每個NSNumber按照BOOL的規則,取非,當成新信號的值。
8. and
- (RACSignal *)and {
return [[self map:^(RACTuple *tuple) {
NSCAssert([tuple isKindOfClass:RACTuple.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple);
NSCAssert(tuple.count > 0, @"-and must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple");
return @([tuple.rac_sequence all:^(NSNumber *number) {
NSCAssert([number isKindOfClass:NSNumber.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple);
return number.boolValue;
}]);
}] setNameWithFormat:@"[%@] -and", self.name];
}
and操作需要原信號的每個信號都是元組RACTuple類型的,因為只有這樣,RACTuple類型里面的每個元素的值才能進行&運算。
and操作里面有3處斷言。第一處,判斷入參是不是元組RACTuple類型的。第二處,判斷RACTuple類型里面至少包含一個NSNumber。第三處,判斷RACTuple里面是否都是NSNumber類型,有一個不符合,都會報錯。
- (RACSequence *)rac_sequence {
return [RACTupleSequence sequenceWithTupleBackingArray:self.backingArray offset:0];
}
RACTuple類型先轉換成RACTupleSequence。
+ (instancetype)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset {
NSCParameterAssert(offset <= backingArray.count);
if (offset == backingArray.count) return self.empty;
RACTupleSequence *seq = [[self alloc] init];
seq->_tupleBackingArray = backingArray;
seq->_offset = offset;
return seq;
}
backingArray是一個數組NSArry。這里關于RACTupleSequence和RACTuple會在以后的文章中詳細分析,本篇以分析RACSignal為主。
RACTuple類型先轉換成RACTupleSequence,即存成了一個數組。
- (BOOL)all:(BOOL (^)(id))block {
NSCParameterAssert(block != NULL);
NSNumber *result = [self foldLeftWithStart:@YES reduce:^(NSNumber *accumulator, id value) {
return @(accumulator.boolValue && block(value));
}];
return result.boolValue;
}
- (id)foldLeftWithStart:(id)start reduce:(id (^)(id, id))reduce {
NSCParameterAssert(reduce != NULL);
if (self.head == nil) return start;
for (id value in self) {
start = reduce(start, value);
}
return start;
}
for會遍歷RACSequence里面存的每一個值,分別都去調用reduce( )閉包。start的初始值為YES。reduce( )閉包是:
^(NSNumber *accumulator, id value) { return @(accumulator.boolValue && block(value)); }
這里又會去調用block( )閉包:
^(NSNumber *number) { return number.boolValue; }
number是原信號RACTuple的第一個值。第一次循環reduce( )閉包是拿YES和原信號RACTuple的第一個值進行&計算。第二個循環reduce( )閉包是拿原信號RACTuple的第一個值和第二個值進行&計算,得到的值參與下一次循環,與第三個值進行&計算,如此下去。這也是折疊函數的意思,foldLeft從左邊開始折疊。fold函數會從左至右,把RACTuple轉換成的數組里面每個值都一個接著一個進行&計算。
每個RACTuple都map成這樣的一個BOOL值。接下去信號就map成了一個新的信號。
9. or
- (RACSignal *)or {
return [[self map:^(RACTuple *tuple) {
NSCAssert([tuple isKindOfClass:RACTuple.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple);
NSCAssert(tuple.count > 0, @"-or must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple");
return @([tuple.rac_sequence any:^(NSNumber *number) {
NSCAssert([number isKindOfClass:NSNumber.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple);
return number.boolValue;
}]);
}] setNameWithFormat:@"[%@] -or", self.name];
}
or操作的實現和and操作的實現大體類似。3處斷言的作用和and操作完全一致,這里就不再贅述了。or操作的重點在any函數的實現上。or操作的入參也必須是RACTuple類型的。
- (BOOL)any:(BOOL (^)(id))block {
NSCParameterAssert(block != NULL);
return [self objectPassingTest:block] != nil;
}
- (id)objectPassingTest:(BOOL (^)(id))block {
NSCParameterAssert(block != NULL);
return [self filter:block].head;
}
- (instancetype)filter:(BOOL (^)(id value))block {
NSCParameterAssert(block != nil);
Class class = self.class;
return [[self flattenMap:^ id (id value) {
if (block(value)) {
return [class return:value];
} else {
return class.empty;
}
}] setNameWithFormat:@"[%@] -filter:", self.name];
}
any會依次判斷RACTupleSequence數組里面的值,依次每個進行filter。如果value對應的BOOL值是YES,就轉換成一個RACTupleSequence信號。如果對應的是NO,則轉換成一個empty信號。
只要RACTuple為NO,就一直返回empty信號,直到BOOL值為YES,就返回1。map變換信號后變成成1。找到了YES之后的值就不會再判斷了。如果沒有找到YES,中間都是NO的話,一直遍歷到數組最后一個,信號只能返回0。
10. any:
- (RACSignal *)any:(BOOL (^)(id object))predicateBlock {
NSCParameterAssert(predicateBlock != NULL);
return [[[self materialize] bind:^{
return ^(RACEvent *event, BOOL *stop) {
if (event.finished) {
*stop = YES;
return [RACSignal return:@NO];
}
if (predicateBlock(event.value)) {
*stop = YES;
return [RACSignal return:@YES];
}
return [RACSignal empty];
};
}] setNameWithFormat:@"[%@] -any:", self.name];
}
原信號會先經過materialize轉換包裝成RACEvent事件。依次判斷predicateBlock(event.value)值的BOOL值,如果返回YES,就包裝成RACSignal的新信號,發送YES出去,并且stop接下來的信號。如果返回MO,就返回[RACSignal empty]空信號。直到event.finished,返回[RACSignal return:@NO]。
所以any:操作的目的是找到第一個滿足predicateBlock條件的值。找到了就返回YES的RACSignal的信號,如果沒有找到,返回NO的RACSignal。
11. any
- (RACSignal *)any {
return [[self any:^(id x) {
return YES;
}] setNameWithFormat:@"[%@] -any", self.name];
}
any操作是any:操作中的一種情況。即predicateBlock閉包永遠都返回YES,所以any操作之后永遠都只能得到一個只發送一個YES的新信號。
12. all:
- (RACSignal *)all:(BOOL (^)(id object))predicateBlock {
NSCParameterAssert(predicateBlock != NULL);
return [[[self materialize] bind:^{
return ^(RACEvent *event, BOOL *stop) {
if (event.eventType == RACEventTypeCompleted) {
*stop = YES;
return [RACSignal return:@YES];
}
if (event.eventType == RACEventTypeError || !predicateBlock(event.value)) {
*stop = YES;
return [RACSignal return:@NO];
}
return [RACSignal empty];
};
}] setNameWithFormat:@"[%@] -all:", self.name];
}
all:操作和any:有點類似。原信號會先經過materialize轉換包裝成RACEvent事件。對原信號發送的每個信號都依次判斷predicateBlock(event.value)是否是NO 或者event.eventType == RACEventTypeError。如果predicateBlock(event.value)返回NO或者出現了錯誤,新的信號都返回NO。如果一直都沒出現問題,在RACEventTypeCompleted的時候發送YES。
all:可以用來判斷整個原信號發送過程中是否有錯誤事件RACEventTypeError,或者是否存在predicateBlock為NO的情況??梢园裵redicateBlock設置成一個正確條件。如果原信號出現錯誤事件,或者不滿足設置的錯誤條件,都會發送新信號返回NO。如果全過程都沒有出錯,或者都滿足predicateBlock設置的條件,則一直到RACEventTypeCompleted,發送YES的新信號。
13. repeat
- (RACSignal *)repeat {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
return subscribeForever(self,
^(id x) {
[subscriber sendNext:x];
},
^(NSError *error, RACDisposable *disposable) {
[disposable dispose];
[subscriber sendError:error];
},
^(RACDisposable *disposable) {
// Resubscribe.
});
}] setNameWithFormat:@"[%@] -repeat", self.name];
}
repeat操作返回一個subscribeForever閉包,閉包里面要傳入4個參數。
static RACDisposable *subscribeForever (RACSignal *signal, void (^next)(id), void (^error)(NSError *, RACDisposable *), void (^completed)(RACDisposable *)) {
next = [next copy];
error = [error copy];
completed = [completed copy];
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
RACSchedulerRecursiveBlock recursiveBlock = ^(void (^recurse)(void)) {
RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable];
[compoundDisposable addDisposable:selfDisposable];
__weak RACDisposable *weakSelfDisposable = selfDisposable;
RACDisposable *subscriptionDisposable = [signal subscribeNext:next error:^(NSError *e) {
@autoreleasepool {
error(e, compoundDisposable);
[compoundDisposable removeDisposable:weakSelfDisposable];
}
recurse();
} completed:^{
@autoreleasepool {
completed(compoundDisposable);
[compoundDisposable removeDisposable:weakSelfDisposable];
}
recurse();
}];
[selfDisposable addDisposable:subscriptionDisposable];
};
// Subscribe once immediately, and then use recursive scheduling for any
// further resubscriptions.
recursiveBlock(^{
RACScheduler *recursiveScheduler = RACScheduler.currentScheduler ?: [RACScheduler scheduler];
RACDisposable *schedulingDisposable = [recursiveScheduler scheduleRecursiveBlock:recursiveBlock];
[compoundDisposable addDisposable:schedulingDisposable];
});
return compoundDisposable;
}
subscribeForever有4個參數,第一個參數是原信號,第二個是傳入的next閉包,第三個是error閉包,最后一個是completed閉包。
subscribeForever一進入這個函數就會調用recursiveBlock( )閉包,閉包中有一個recurse( )的入參的參數。在recursiveBlock( )閉包中對原信號RACSignal進行訂閱。next,error,completed分別會先調用傳進來的閉包。然后error,completed執行完error( )和completed( )閉包之后,還會繼續再執行recurse( ),recurse( )是recursiveBlock的入參。
- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
[self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable];
return disposable;
}
- (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable {
@autoreleasepool {
RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable];
[disposable addDisposable:selfDisposable];
__weak RACDisposable *weakSelfDisposable = selfDisposable;
RACDisposable *schedulingDisposable = [self schedule:^{ // 此處省略 }];
[selfDisposable addDisposable:schedulingDisposable];
}
}
先取到當前的currentScheduler,即recursiveScheduler,執行scheduleRecursiveBlock,在這個函數中,會調用schedule函數。這里的recursiveScheduler是RACQueueScheduler類型的。
- (RACDisposable *)schedule:(void (^)(void))block {
NSCParameterAssert(block != NULL);
RACDisposable *disposable = [[RACDisposable alloc] init];
dispatch_async(self.queue, ^{
if (disposable.disposed) return;
[self performAsCurrentScheduler:block];
});
return disposable;
}
如果原信號沒有disposed,dispatch_async會繼續執行block,在這個block中還會繼續原信號的發送。所以原信號只要沒有error信號,disposable.disposed就不會返回YES,就會一直調用block。所以在subscribeForever的error和completed的最后都會調用recurse( )閉包。error調用recurse( )閉包是為了結束調用block,結束所有的信號。completed調用recurse( )閉包是為了繼續調用block( )閉包,也就是repeat的本質。原信號會繼續發送信號,如此無限循環下去。
14. retry:
- (RACSignal *)retry:(NSInteger)retryCount {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
__block NSInteger currentRetryCount = 0;
return subscribeForever(self,
^(id x) {
[subscriber sendNext:x];
},
^(NSError *error, RACDisposable *disposable) {
if (retryCount == 0 || currentRetryCount < retryCount) {
// Resubscribe.
currentRetryCount++;
return;
}
[disposable dispose];
[subscriber sendError:error];
},
^(RACDisposable *disposable) {
[disposable dispose];
[subscriber sendCompleted];
});
}] setNameWithFormat:@"[%@] -retry: %lu", self.name, (unsigned long)retryCount];
}
在retry:的實現中,和repeat實現的區別是中間加入了一個currentRetryCount值。如果currentRetryCount > retryCount的話,就會在error中調用[disposable dispose],這樣subscribeForever就不會再無限循環下去了。
所以retry:操作的用途就是在原信號在出現error的時候,重試retryCount的次數,如果依舊error,那么就會停止重試。
如果原信號沒有發生錯誤,那么原信號在發送結束,subscribeForever也就結束了。retry:操作對于沒有任何error的信號相當于什么都沒有發生。
15. retry
- (RACSignal *)retry {
return [[self retry:0] setNameWithFormat:@"[%@] -retry", self.name];
}
這里的retry操作就是一個無限重試的操作。因為retryCount設置成0之后,在error的閉包中中,retryCount 永遠等于 0,原信號永遠都不會被dispose,所以subscribeForever會一直無限重試下去。
同樣的,如果對一個沒有error的信號調用retry操作,也是不起任何作用的。
16. scanWithStart: reduceWithIndex: (在父類RACStream中定義的)
先寫出測試代碼:
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
{
[subscriber sendNext:@1];
[subscriber sendNext:@1];
[subscriber sendNext:@4];
return [RACDisposable disposableWithBlock:^{
}];
}];
RACSignal *signalB = [signalA scanWithStart:@(2) reduceWithIndex:^id(NSNumber * running, NSNumber * next, NSUInteger index) {
return @(running.intValue * next.intValue + index);
}];
2 // 2 * 1 + 0 = 2
3 // 2 * 1 + 1 = 3
14 // 3 * 4 + 2 = 14
- (instancetype)scanWithStart:(id)startingValue reduceWithIndex:(id (^)(id, id, NSUInteger))reduceBlock {
NSCParameterAssert(reduceBlock != nil);
Class class = self.class;
return [[self bind:^{
__block id running = startingValue;
__block NSUInteger index = 0;
return ^(id value, BOOL *stop) {
running = reduceBlock(running, value, index++);
return [class return:running];
};
}] setNameWithFormat:@"[%@] -scanWithStart: %@ reduceWithIndex:", self.name, [startingValue rac_description]];
}
scanWithStart這個變換由初始值,變換函數reduceBlock( ),和index步進的變量組成。原信號的每個信號都會由變換函數reduceBlock( )進行變換。index每次都是自增。變換的初始值是由入參startingValue傳入的。
17. scanWithStart: reduce: (在父類RACStream中定義的)
- (instancetype)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))reduceBlock {
NSCParameterAssert(reduceBlock != nil);
return [[self
scanWithStart:startingValue
reduceWithIndex:^(id running, id next, NSUInteger index) {
return reduceBlock(running, next);
}]
setNameWithFormat:@"[%@] -scanWithStart: %@ reduce:", self.name, [startingValue rac_description]];
}
scanWithStart: reduce:就是scanWithStart: reduceWithIndex: 的縮略版。變換函數也是外面閉包reduceBlock( )傳進來的。只不過變換過程中不會使用index自增的這個變量。
18. aggregateWithStart: reduceWithIndex:
- (RACSignal *)aggregateWithStart:(id)start reduceWithIndex:(id (^)(id, id, NSUInteger))reduceBlock {
return [[[[self
scanWithStart:start reduceWithIndex:reduceBlock]
startWith:start]
takeLast:1]
setNameWithFormat:@"[%@] -aggregateWithStart: %@ reduceWithIndex:", self.name, [start rac_description]];
}
aggregate是合計的意思。所以最后變換出來的信號只有最后一個值。
aggregateWithStart: reduceWithIndex:操作調用了scanWithStart: reduceWithIndex:,原理和它完全一致。不同的是多了兩步額外的操作,一個是startWith:,一個是takeLast:1。startWith:是在scanWithStart: reduceWithIndex:變換之后的信號之前加上start信號。takeLast:1是取最后一個信號。takeLast:和startWith:的詳細分析文章下面會詳述。
值得注意的一點是,原信號如果沒有發送complete信號,那么該函數就不會輸出新的信號值。因為在一直等待結束。
19. aggregateWithStart: reduce:
- (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock {
return [[self
aggregateWithStart:start
reduceWithIndex:^(id running, id next, NSUInteger index) {
return reduceBlock(running, next);
}]
setNameWithFormat:@"[%@] -aggregateWithStart: %@ reduce:", self.name, [start rac_description]];
}
aggregateWithStart: reduce:調用aggregateWithStart: reduceWithIndex:函數,只不過沒有只用index值。同樣,如果原信號沒有發送complete信號,也不會輸出任何信號。
20. aggregateWithStartFactory: reduce:
- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory reduce:(id (^)(id running, id next))reduceBlock {
NSCParameterAssert(startFactory != NULL);
NSCParameterAssert(reduceBlock != NULL);
return [[RACSignal defer:^{
return [self aggregateWithStart:startFactory() reduce:reduceBlock];
}] setNameWithFormat:@"[%@] -aggregateWithStartFactory:reduce:", self.name];
}
aggregateWithStartFactory: reduce:內部實現就是調用aggregateWithStart: reduce:,只不過入參多了一個產生start的startFactory( )閉包罷了。
21. collect
- (RACSignal *)collect {
return [[self aggregateWithStartFactory:^{
return [[NSMutableArray alloc] init];
} reduce:^(NSMutableArray *collectedValues, id x) {
[collectedValues addObject:(x ?: NSNull.null)];
return collectedValues;
}] setNameWithFormat:@"[%@] -collect", self.name];
}
collect函數會調用aggregateWithStartFactory: reduce:方法。把所有原信號的值收集起來,保存在NSMutableArray中。
二. 時間操作
1. throttle:valuesPassingTest:
這個操作傳入一個時間間隔NSTimeInterval,和一個判斷條件的閉包predicate。原信號在一個時間間隔NSTimeInterval之間發送的信號,如果還能滿足predicate,則原信號都被“吞”了,直到一個時間間隔NSTimeInterval結束,會再次判斷predicate,如果不滿足了,原信號就會被發送出來。
如上圖,原信號發送1以后,間隔NSTimeInterval的時間內,沒有信號發出,并且predicate也為YES,就把1變換成新的信號發出去。接下去由于原信號發送2,3,4的過程中,都在間隔NSTimeInterval的時間內,所以都被“吞”了。直到原信號發送5之后,間隔NSTimeInterval的時間內沒有新的信號發出,所以把原信號的5發送出來。原信號的6也是如此。
再來看看具體實現:
- (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate {
NSCParameterAssert(interval >= 0);
NSCParameterAssert(predicate != nil);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
RACScheduler *scheduler = [RACScheduler scheduler];
__block id nextValue = nil;
__block BOOL hasNextValue = NO;
RACSerialDisposable *nextDisposable = [[RACSerialDisposable alloc] init];
void (^flushNext)(BOOL send) = ^(BOOL send) { // 暫時省略 };
RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
// 暫時省略
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
flushNext(YES);
[subscriber sendCompleted];
}];
[compoundDisposable addDisposable:subscriptionDisposable];
return compoundDisposable;
}] setNameWithFormat:@"[%@] -throttle: %f valuesPassingTest:", self.name, (double)interval];
}
看這段實現,里面有2處斷言。會先判斷傳入的interval是否大于0,小于0當然是不行的。還有一個就是傳入的predicate閉包不能為空,這個是接下來用來控制流程的。
接下來的實現還是按照套路來,返回值是一個信號,新信號的閉包里面再訂閱原信號進行變換。
那么整個變換的重點就落在了flushNext閉包和訂閱原信號subscribeNext閉包中了。
當新的信號一旦被訂閱,閉包執行到此處,就會對原信號進行訂閱。
[self subscribeNext:^(id x) {
RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler;
BOOL shouldThrottle = predicate(x);
@synchronized (compoundDisposable) {
flushNext(NO);
if (!shouldThrottle) {
[subscriber sendNext:x];
return;
}
nextValue = x;
hasNextValue = YES;
nextDisposable.disposable = [delayScheduler afterDelay:interval schedule:^{
flushNext(YES);
}];
}
}
首先先創建一個delayScheduler。先判斷當前的currentScheduler是否存在,不存在就取之前創建的[RACScheduler scheduler]。這里雖然兩處都是RACTargetQueueScheduler類型的,但是currentScheduler是com.ReactiveCocoa.RACScheduler.mainThreadScheduler,而[RACScheduler scheduler]創建的是com.ReactiveCocoa.RACScheduler.backgroundScheduler。
調用predicate( )閉包,傳入原信號發來的信號值x,經過predicate判斷以后,得到是否打開節流開關的BOOL變量shouldThrottle。
之所以把RACCompoundDisposable作為線程間互斥信號量,因為RACCompoundDisposable里面會加入所有的RACDisposable信號。接著下面的操作用@synchronized給線程間加鎖。
flushNext( )這個閉包是為了hook住原信號的發送。
void (^flushNext)(BOOL send) = ^(BOOL send) {
@synchronized (compoundDisposable) {
[nextDisposable.disposable dispose];
if (!hasNextValue) return;
if (send) [subscriber sendNext:nextValue];
nextValue = nil;
hasNextValue = NO;
}
};
這個閉包中如果傳入的是NO,那么原信號就無法立即sendNext。如果傳入的是YES,并且hasNextValue = YES,原信號待發送的還有值,那么就發送原信號。
shouldThrottle是一個閥門,隨時控制原信號是否可以被發送。
小結一下,每個原信號發送過來,通過在throttle:valuesPassingTest:里面的did subscriber閉包中進行訂閱。這個閉包中主要干了4件事情:
- 調用flushNext(NO)閉包判斷能否發送原信號的值。入參為NO,不發送原信號的值。
- 判斷閥門條件predicate(x)能否發送原信號的值。
- 如果以上兩個條件都滿足,nextValue中進行賦值為原信號發來的值,hasNextValue = YES代表當前有要發送的值。
- 開啟一個delayScheduler,延遲interval的時間,發送原信號的這個值,即調用flushNext(YES)。
現在再來分析一下整個throttle:valuesPassingTest:的全過程
原信號發出第一個值,如果在interval的時間間隔內,沒有新的信號發送,那么delayScheduler延遲interval的時間,執行flushNext(YES),發送原信號的這個第一個值。
- (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block {
return [self after:[NSDate dateWithTimeIntervalSinceNow:delay] schedule:block];
}
- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {
NSCParameterAssert(date != nil);
NSCParameterAssert(block != NULL);
RACDisposable *disposable = [[RACDisposable alloc] init];
dispatch_after([self.class wallTimeWithDate:date], self.queue, ^{
if (disposable.disposed) return;
[self performAsCurrentScheduler:block];
});
return disposable;
}
注意,在dispatch_after閉包里面之前[self performAsCurrentScheduler:block]之前,有一個關鍵的判斷:
if (disposable.disposed) return;
這個判斷就是用來判斷從第一個信號發出,在間隔interval的時間之內,還有沒有其他信號存在。如果有,第一個信號肯定會disposed,這里會執行return,所以也就不會把第一個信號發送出來了。
這樣也就達到了節流的目的:原來每個信號都會創建一個delayScheduler,都會延遲interval的時間,在這個時間內,如果原信號再沒有發送新值,即原信號沒有disposed,就把原信號的值發出來;如果在這個時間內,原信號還發送了一個新值,那么第一個值就被丟棄。在發送過程中,每個信號都要判斷一次predicate( ),這個是閥門的開關,如果隨時都不節流了,原信號發的值就需要立即被發送出來。
還有二點需要注意的是,第一點,正好在interval那一時刻,有新信號發送出來,原信號也會被丟棄,即只有在>=interval的時間之內,原信號沒有發送新值,原來的這個值才能發送出來。第二點,原信號發送completed時,會立即執行flushNext(YES),把原信號的最后一個值發送出來。
2. throttle:
- (RACSignal *)throttle:(NSTimeInterval)interval {
return [[self throttle:interval valuesPassingTest:^(id _) {
return YES;
}] setNameWithFormat:@"[%@] -throttle: %f", self.name, (double)interval];
}
這個操作其實就是調用了throttle:valuesPassingTest:方法,傳入時間間隔interval,predicate( )閉包則永遠返回YES,原信號的每個信號都執行節流操作。
3. bufferWithTime:onScheduler:
這個操作的實現是類似于throttle:valuesPassingTest:的實現。
- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler {
NSCParameterAssert(scheduler != nil);
NSCParameterAssert(scheduler != RACScheduler.immediateScheduler);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACSerialDisposable *timerDisposable = [[RACSerialDisposable alloc] init];
NSMutableArray *values = [NSMutableArray array];
void (^flushValues)() = ^{
// 暫時省略
};
RACDisposable *selfDisposable = [self subscribeNext:^(id x) {
// 暫時省略
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
flushValues();
[subscriber sendCompleted];
}];
return [RACDisposable disposableWithBlock:^{
[selfDisposable dispose];
[timerDisposable dispose];
}];
}] setNameWithFormat:@"[%@] -bufferWithTime: %f onScheduler: %@", self.name, (double)interval, scheduler];
}
bufferWithTime:onScheduler:的實現和throttle:valuesPassingTest:的實現給出類似。開始有2個斷言,2個都是判斷scheduler的,第一個斷言是判斷scheduler是否為nil。第二個斷言是判斷scheduler的類型的,scheduler類型不能是immediateScheduler類型的,因為這個方法是要緩存一些信號的,所以不能是immediateScheduler類型的。
RACDisposable *selfDisposable = [self subscribeNext:^(id x) {
@synchronized (values) {
if (values.count == 0) {
timerDisposable.disposable = [scheduler afterDelay:interval schedule:flushValues];
}
[values addObject:x ?: RACTupleNil.tupleNil];
}
}
在subscribeNext中,當數組里面是沒有存任何原信號的值,就會開啟一個scheduler,延遲interval時間,執行flushValues閉包。如果里面有值了,就繼續加到values的數組中。關鍵的也是閉包里面的內容,代碼如下:
void (^flushValues)() = ^{
@synchronized (values) {
[timerDisposable.disposable dispose];
if (values.count == 0) return;
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:values];
[values removeAllObjects];
[subscriber sendNext:tuple];
}
};
flushValues( )閉包里面主要是把數組包裝成一個元組,并且全部發送出來,原數組里面就全部清空了。這也是bufferWithTime:onScheduler:的作用,在interval時間內,把這個時間間隔內的原信號都緩存起來,并且在interval的那一刻,把這些緩存的信號打包成一個元組,發送出來。
和throttle:valuesPassingTest:方法一樣,在原信號completed的時候,立即執行flushValues( )閉包,把里面存的值都發送出來。
4. delay:
delay:函數的操作和上面幾個套路都是一樣的,實現方式也都是模板式的,唯一的不同都在subscribeNext中,和一個判斷是否發送的閉包中。
- (RACSignal *)delay:(NSTimeInterval)interval {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
// We may never use this scheduler, but we need to set it up ahead of
// time so that our scheduled blocks are run serially if we do.
RACScheduler *scheduler = [RACScheduler scheduler];
void (^schedule)(dispatch_block_t) = ^(dispatch_block_t block) {
// 暫時省略
};
RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
// 暫時省略
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
schedule(^{
[subscriber sendCompleted];
});
}];
[disposable addDisposable:subscriptionDisposable];
return disposable;
}] setNameWithFormat:@"[%@] -delay: %f", self.name, (double)interval];
}
在delay:的subscribeNext中,就單純的執行了schedule的閉包。
RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) {
schedule(^{
[subscriber sendNext:x];
});
}
void (^schedule)(dispatch_block_t) = ^(dispatch_block_t block) {
RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler;
RACDisposable *schedulerDisposable = [delayScheduler afterDelay:interval schedule:block];
[disposable addDisposable:schedulerDisposable];
};
在schedule閉包中做的時間就是延遲interval的時間發送原信號的值。
5. interval:onScheduler:withLeeway:
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway {
NSCParameterAssert(scheduler != nil);
NSCParameterAssert(scheduler != RACScheduler.immediateScheduler);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
return [scheduler after:[NSDate dateWithTimeIntervalSinceNow:interval] repeatingEvery:interval withLeeway:leeway schedule:^{
[subscriber sendNext:[NSDate date]];
}];
}] setNameWithFormat:@"+interval: %f onScheduler: %@ withLeeway: %f", (double)interval, scheduler, (double)leeway];
}
在這個操作中,實現代碼不難。先來看看2個斷言,都是保護入參類型的,scheduler不能為空,且不能是immediateScheduler的類型,原因和上面是一樣的,這里是延遲操作。
主要的實現就在after:repeatingEvery:withLeeway:schedule:上了。
- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {
NSCParameterAssert(date != nil);
NSCParameterAssert(interval > 0.0 && interval < INT64_MAX / NSEC_PER_SEC);
NSCParameterAssert(leeway >= 0.0 && leeway < INT64_MAX / NSEC_PER_SEC);
NSCParameterAssert(block != NULL);
uint64_t intervalInNanoSecs = (uint64_t)(interval * NSEC_PER_SEC);
uint64_t leewayInNanoSecs = (uint64_t)(leeway * NSEC_PER_SEC);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue);
dispatch_source_set_timer(timer, [self.class wallTimeWithDate:date], intervalInNanoSecs, leewayInNanoSecs);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
return [RACDisposable disposableWithBlock:^{
dispatch_source_cancel(timer);
}];
}
這里的實現就是用GCD在self.queue上創建了一個Timer,時間間隔是interval,修正時間是leeway。
leeway這個參數是為dispatch source指定一個期望的定時器事件精度,讓系統能夠靈活地管理并喚醒內核。例如系統可以使用leeway值來提前或延遲觸發定時器,使其更好地與其它系統事件結合。創建自己的定時器時,應該盡量指定一個leeway值。不過就算指定leeway值為0,也不能完完全全期望定時器能夠按照精確的納秒來觸發事件。
這個定時器在interval執行sendNext操作,也就是發送原信號的值。
6. interval:onScheduler:
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler {
return [[RACSignal interval:interval onScheduler:scheduler withLeeway:0.0] setNameWithFormat:@"+interval: %f onScheduler: %@", (double)interval, scheduler];
}
這個操作就是調用上一個方法interval:onScheduler:withLeeway:,只不過leeway = 0.0。具體實現上面已經分析過了,這里不再贅述。
最后
本來想窮盡分析每一個RACSignal的操作的實現,但是發現所有操作加起來實在太多,用一篇文章全部寫完篇幅太長了,簡書一篇放不下了,還是拆成幾篇,RACSignal還剩過濾操作,多信號組合操作,冷熱信號轉換操作,高階信號操作,下篇接著繼續分析。最后請大家多多指教。