ReactiveCocoa 学习 - 6

RAC的信号的基础操作的介绍

2016-06-13 | 阅读

Basic Operators

介绍几个简单的基础的操作符.

Performing side effects with signals

Subscription

使用 subscribe命令来根据信号的当前或未来的值设定响应操作:

ACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;

// Outputs: A B C D E F G H I
[letters subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];

在冷信号中,每次订阅信号都会执行副作用.

Injecting effects

使用 do...命令,在不订阅信号的情况下,添加副作用.

__block unsigned subscriptions = 0;

RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
    subscriptions++;
    [subscriber sendCompleted];
    return nil;
}];

// Does not output anything yet
loggingSignal = [loggingSignal doCompleted:^{
    NSLog(@"about to complete subscription %u", subscriptions);
}];

// Outputs:
// about to complete subscription 1
// subscription 1
[loggingSignal subscribeCompleted:^{
    NSLog(@"subscription %u", subscriptions);
}];

do的操作会比 subscribe要先执行.

Transforming streams

这些操作,将一个信号流转变为一个新的信号流.

Mapping

使用map:命令,将信号的值进行替换.

// 以" "为间隔符创建sequence
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;

// Contains: AA BB CC DD EE FF GG HH II
RACSequence *mapped = [letters map:^(NSString *value) {
    return [value stringByAppendingString:value];
}];

Filtering

使用filter:命令,过滤信号值,过滤NO的信号值:

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;

// Contains: 2 4 6 8
RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) {
    return (value.intValue % 2) == 0;
}];

Combining streams

将多个信号流聚合成一个信号流

Concatenating

使用 concat:命令,将一个信号的值接在另一个信号后面:

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;

// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *concatenated = [letters concat:numbers];

Flattening

使用flatten:命令,对于信号中的信号,将 信号的值整合进一个新的信号流. 如下,连接Sequence时:

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *sequenceOfSequences = @[ letters, numbers ].rac_sequence;

// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *flattened = [sequenceOfSequences flatten];

这里 sequenceOfSequences,这个信号的值 是一个信号,使用flatten,将信号中的信号的值 给取出来,作为信号的值,创建一个新的信号.

再聚一个聚合信号的例子:

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
    [subscriber sendNext:letters];
    [subscriber sendNext:numbers];
    [subscriber sendCompleted];
    return nil;
}];

RACSignal *flattened = [signalOfSignals flatten];

// Outputs: A 1 B C 2
[flattened subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];

[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];

Mapping and flattening

使用flatten的目的,一般并不是为了这个效果,而是为了操作flattenMap:.

flattenMap:命令,用于转换多个信号流中的信号值输出一个新的信号流.从字面意思上来解释,这个操作就是, 先map操作,处理信号,然后再flatten操作,将信号中的信号提取出来作为一个完整的信号. 也就是说,block中return的是一个信号,而flattenMap返回值是一个信号,将return中的信号flatten后的一个完整的信号.还是用sequence来举例:

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;

// Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
RACSequence *extended = [numbers flattenMap:^(NSString *num) {
    return @[ num, num ].rac_sequence;
}];

// Contains: 1_ 3_ 5_ 7_ 9_
RACSequence *edited = [numbers flattenMap:^(NSString *num) {
    if (num.intValue % 2 == 0) {
        return [RACSequence empty];
    } else {
        NSString *newNum = [num stringByAppendingString:@"_"];
        return [RACSequence return:newNum]; 
    }
}];

flattenMap也用于将复数个信号的工作自动的结合在一起:

RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;

[[letters
    flattenMap:^(NSString *letter) {
        return [database saveEntriesForLetter:letter];
    }]
    subscribeCompleted:^{
        NSLog(@"All database entries saved successfully.");
    }];

Combining signals

将多个信号聚合成一个信号.

Sequencing

使用then:命令, 一般为 signalA then:^{return signalB},表示 订阅signalA的信号,但是忽略所有的next事件,当completed事件发送时,订阅B信号并返回B信号的事件.

RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;

// The new signal only contains: 1 2 3 4 5 6 7 8 9
//
// But when subscribed to, it also outputs: A B C D E F G H I
RACSignal *sequenced = [[letters
    doNext:^(NSString *letter) {
        NSLog(@"%@", letter);
    }]
    then:^{
        return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal;
    }];

使用场景, 执行完前一个信号的所有副作用,然后开始另一个新信号,将其返回值作为真正的信号值 传递.

Merging

merge:命令,如名字一样,合并信号,但是,根据的是信号值到来的顺序:

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *merged = [RACSignal merge:@[ letters, numbers ]];

// Outputs: A 1 B C 2
[merged subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];

[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];

Combining lastest values

combineLastest:combineLastest:reduce:两个方法,用来聚合信号,当多个信号中的任何一个信号发生变化时,都会取每个信号的最新值,组合成一个新的值发送.而两个方法的区别在于,前者传递的信号是一个RACTuple对象,将多个信号的值封装在一个对象中,而后者在reduce的block中来处理多个信号的值,将其整合成一个值返回.

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *combined = [RACSignal
    combineLatest:@[ letters, numbers ]
    // reduce的block的参数可以自行添加,但顺序要与combineLatest中信号的顺序相同.
    reduce:^(NSString *letter, NSString *number) {
        return [letter stringByAppendingString:number];
    }];

// Outputs: B1 B2 C2 C3
[combined subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
[numbers sendNext:@"2"];
[letters sendNext:@"C"];
[numbers sendNext:@"3"];

一定要注意这里,聚合的所有信号都有第一个值后,这个聚合信号才会发送第一个值,所以这里letters发送的第一个信号A,不会在聚合信号combined中出现.

然后就要与另外一个方法zip进行比较.

zip stream

压缩信号.与上面的combineLastest有些相似,但是区别在于, zip需要等待多个信号都有一个最新的值后,才会发送一个信号,而combine中,任何一个信号有新的值后,都会发送信号.

举例,还是类似combineLatest中的例子,只是将combineLatest改为zip,但是结果就变了.

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *combined = [RACSignal
                       zip:@[ letters, numbers ]
                       reduce:^(NSString *letter, NSString *number) {
                           return [letter stringByAppendingString:number];
                       }];

// Outputs: A1 B2 C3
[combined subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
[numbers sendNext:@"2"];
[letters sendNext:@"C"];
[numbers sendNext:@"3"];

在这里,最终输出地结果是A1,B2,C3.

Switching

使用switchToLastest来获取信号中的信号的值,传递nexterror,但是不传递complete:

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSubject *signalOfSignals = [RACSubject subject];

RACSignal *switched = [signalOfSignals switchToLatest];

// Outputs: A B 1 D
[switched subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];

[signalOfSignals sendNext:letters];
[letters sendNext:@"A"];
[letters sendNext:@"B"];

[signalOfSignals sendNext:numbers];
[letters sendNext:@"C"];
[numbers sendNext:@"1"];

[signalOfSignals sendNext:letters];
[numbers sendNext:@"2"];
[letters sendNext:@"D"];

当信号中的信号发送complete时,由于switchToLastest不接收这个completed事件,所以 switchToLastest的信号依旧继续处理.但是当信号中的信号发送error时,会中断switchToLastest的信号处理:

RACSubject *signalOfSignals = [RACSubject subject];
RACSignal *switched = [signalOfSignals switchToLatest];

[switched subscribeNext:^(id x) {
    NSLog(@"Next : %@",x);
} error:^(NSError *error) {
    NSLog(@"Error : %@",error);
} completed:^{
    NSLog(@"Completed!");
}];

RACSubject *signalA = [RACSubject subject];
[signalOfSignals sendNext:signalA];
[signalA sendNext:@"A"];
[signalA sendCompleted];
[signalA sendNext:@"AA"];

RACSubject *signalB = [RACSubject subject];
[signalOfSignals sendNext:signalB];
[signalB sendNext:@"B"];
[signalB sendError:[NSError errorWithDomain:@"error" code:1 userInfo:nil]];
[signalB sendNext:@"BB"];

RACSubject *signalC = [RACSubject subject];
[signalOfSignals sendNext:signalC];
[signalC sendNext:@"C"];

上面这段代码示例中,输出结果为 :

Next : A
Next : B
Error : Error Domain=error Code=1 "(null)"

信号A发送完成后,AA的信号就无法发送给signalOfSignals了,但是一个新的信号B还是能够继续发信号给signalOfSignals的.但当信号B发送error后,这个signalOfSignals会接受到这个error,从而结束订阅.