响应式编程框架ReactiveCocoa学习——基本操作符

我在上一篇博客中《响应式编程框架ReactiveCocoa介绍与入门》简单介绍了ReactiveCocoa的介绍和简单使用,主要是翻译了官方文档中的README部分,其实个人认为技术最好的学习方式就是去看官方文档。今天我仍旧来翻译官方文档中的BasicOperators部分,也就是基本操作符。我写的一部分代码示例上传至 https://github.com/chenyufeng1991/ReactiveCocoaDemo,欢迎大家下载查看。

这篇文档主要讲解了在ReactiveCocoa使用中常见的操作符,包括它们如何使用的例子。运用到sequence和signal中的被认为是一种流操作符。

【使用信号的副作用】

大多数信号是一种冷信号(cold),也就是说它们不会做任何操作,直到它们被订阅(subscription)。当被订阅以后,信号和订阅者可以使用副作用,比如控制台打印日志,网络请求,或者更新用户界面等等。副作用也可以被注入到信号中,可以让这个信号不是立即执行,而是到每一个订阅发生后才去执行。

1.订阅(Subscription)

-subscribe...方法用来访问信号中当前或未来的值。如下示例:

RACSignal *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);
}];
结果会打印出A B C D E F G H I. 对于冷信号,副作用将会在每次订阅的时候都会发生。如下示例:
__block unsigned subscriptions = 0;

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

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

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

第一次订阅的时候也就是调用subscribeComplete,会输出subscription 1,第二次调用subscribeComplete的时候也就是第二次订阅,会输出subscription 2. 如果不想使用这种副作用的特性,可以使用connect,后面会讲到。


2.注入影响

-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);
}];


【传输流】

下面的这些操作可以转变一个流到另一种流。

1.Mapping 映射

-map:方法传输值到流中,然后创建一个新的流作为结果:

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];
}];
输出内容为"AA BB CC DD EE FF GG HH II".


2.Filtering 过滤

-filter方法用来测试block中的值,只有当符合要求测试通过才可以作为结果流输出。

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;
}];

【组合流】
以下这些操作符可以组合多个流成为一个新的流输出。

1.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];
输出结果:A B C D E F G H I 1 2 3 4 5 6 7 8 9 .


2.Flattening 降阶

-flatten 操作符可以运用到“流的流”,结合它们的值作为新的流。


序列的连接 :

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];
代码中可以看到,本来sequenceOfSequence是“序列的序列”,也就是流的流。是letters和numbers两种流的组合,然后使用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"];
代码中的signalOfSignal是letters和numbers两个信号的合并,然后通过flatten操作降阶,然后在转变成flattened这个信号,当letters和numbers这两个信号分别发送数据的时候,降阶后的flattened信号就能接收到。

3.Mapping and flattening 映射和降阶

Flattening操作最重要的是去理解在-FlattenMap中的使用。

-flattenMap:是用来转变每一个流的值成为一个新的流。因此,所有流的返回可以返回降阶为新的流。换句话说,先执行-flatten操作,再执行-map操作。这可以用来扩展和编辑序列:

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]; 
    }
}];


或者也可以来多个可以自动组合的信号:
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.");
    }];


【组合信号】

以下的操作符可以把多个信号组合成新的RACSignal信号。

1.序列化

-then:开始于一个原始的信号,并等待它完成,然后转发到新的信号。

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;
    }];
这种方式用来处理信号的副作用非常有用,开始于一个信号,但是返回的却是第二个信号的值。

2.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"];

3.Combining latest values 组合最新值

+combineLatest:方法和+combineLatest:reduce:方法可以监听多个信号值的改变,当这些信号中的值改变使就能发送最新值。

RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *combined = [RACSignal
    combineLatest:@[ letters,numbers ]
    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"];
输出结果:“B1 B2 C2 C3”,这里的输出优点奇怪,我们来分析一下。可以看到组合信号只会在每一个信号至少发送一次后才会去发送第一个值。在例子中,@"A"始终没有被打印是因为前面没有接收到numbers信号。


4.Switching 切换

-switchToLatest操作符运用到“信号的信号”,用来转发最新信号中的值。

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"];

来简单分析一下结果,这里会打印出“A B 1 D”. switchToLatest是对于信号而言的,会让switched这个信号切换到最新改变的那个信号。当第一次sendNext:letters时,表示切换到letters信号,此时最新的就是letters信号,所以可以打印出A B。 然后第二次sendNext:numbers时,表示当前改变的最新信号是numbers信号,所以当发送C的时候其实我是接收不到的,所以不会打印C,只会打印1。第三次时的情况和第二次则是类似的。



本文大部分翻译自 https://github.com/ReactiveCocoa/ReactiveCocoa/blob/v2.5/Documentation/BasicOperators.md

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom