首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS开发进阶篇——FRP与ReactiveCocoa的介绍(一)

iOS开发进阶篇——FRP与ReactiveCocoa的介绍(一)

作者头像
企鹅号小编
发布2018-01-08 18:41:20
5840
发布2018-01-08 18:41:20
举报
文章被收录于专栏:企鹅号快讯企鹅号快讯

*****阅读完此文,大约需要30分钟*****

一、FRP的概念

RAC(ReactiveCocoa)是由GitHub团队开发的基于Cocoa的FRP框架。提起FRP,即Functional Reactive Programming(函数式响应式编程),在很多开发领域都有广泛的应用,例如android或者后端开发中有RxJava,尤其是在前端的领域中(react、ajax、vue等框架)应用更为广泛。在iOS移动端目前应用最为经典的就是RAC。

接触RAC之后,我的感觉就是,RAC算得上是业务开发的利器,它的应用可以极大地提高编程开发效率, 它目前在美团、点评客户端开发中,应用比较广泛,已经有很多经典的组件化方案都是基于RAC的。虽说它的学习成本很高(据说一个2~3年经验的iOS开发者,想要精通RAC,也需要2~3月时间,感觉有点夸张),但是把RAC常用的一些操作掌握并应用到业务开发中,还是不难的。下面是RAC github官方地址:

https://github.com/ReactiveCocoa/ReactiveCocoa

二、信号的概念

介绍信号之前,不得不扯点函数响应式编程的概念,如下面:

y = a+b+c;

一个或者多个输入,对应到唯一的y输出,这样便构成一个高阶函数。如果a、b、c中一个或者多个变化(输入变化),y也要跟着变化。通常我们会等a、b、c变化结束后,再一起计算最终的结果y,a、b、c变化结束后并不会及时带来y的变化;如果我们将a、b、c的变化和y提前做好映射(绑定),a、b、c任何一个变化y都能同时映射到对应的变化,便是函数响应式编程。在此基础之上,我们将a、b、c的变化进行抽象封装,为什么要抽象封装呢?因为只有这样,我们才好统一进行管理,而这个抽象封装出来的东西,便构成了信号(Signal)。举个简单的栗子:我们构造了三个输入框,产生a、b、c三个值,正常情况下,当a变化时,y会随后发生变化,如图:

测试代码如下:

@property (nonatomic, strong) UITextField *myTextField1;

@property (nonatomic, strong) UITextField *myTextField2;

@property (nonatomic, strong) UITextField *myTextField3;

@property (nonatomic, strong) NSString *myTextFieldString1;

@property (nonatomic, strong) NSString *myTextFieldString2;

@property (nonatomic, strong) NSString *myTextFieldString3;

@property (nonatomic, strong) UILabel *resultLabel;

当a或b或c发生变化时,会进行下面的回调:

- (void)onTextFieldChanged:(UITextField *)textField forEvent:(UIEvent *)event

{

if (textField.tag == 1000) {

NSLog(@"Result------->%@",self.resultLabel.text);

self.myTextFieldString1 = textField.text;

NSLog(@"Result------->%@",self.resultLabel.text);

}

if (textField.tag == 1001) {

self.myTextFieldString2 = textField.text;

}

if (textField.tag == 1002) {

self.myTextFieldString3 = textField.text;

}

[self onValueChanged];

NSLog(@"Result------->%@",self.resultLabel.text);

}

- (void)onValueChanged

{

NSInteger result = [self.myTextFieldString1 integerValue] + [self.myTextFieldString2 integerValue] + [self.myTextFieldString3 integerValue];

self.resultLabel.text = [NSString stringWithFormat:@"结果:%@",@(result)];

[self.resultLabel sizeToFit];

}

当1改为10后,打印的结果如下:

2017-12-03 19:44:10.687291+0800 MDProject[43978:1616323] Result------->结果:6

2017-12-03 19:44:10.687471+0800 MDProject[43978:1616323] Result------->结果:6

2017-12-03 19:44:10.688152+0800 MDProject[43978:1616323] Result------->结果:15

响应式编程的示意图,如下:

代码如下:

@weakify(self);

RACSignal *signal1 = [RACObserve(self, myTextFieldString1) distinctUntilChanged];

RACSignal *signal2 = [RACObserve(self, myTextFieldString2) distinctUntilChanged];

RACSignal *signal3 = [RACObserve(self, myTextFieldString3) distinctUntilChanged];

RACSignal *resultSignal = [RACSignal combineLatest:@[signal1, signal2, signal3] reduce:^id(NSString *s1, NSString *s2,NSString *s3){

return [NSString stringWithFormat:@"%@",@([s1 integerValue] + [s2 integerValue] +[s3 integerValue])];

}];

[resultSignal subscribeNext:^(id x) {

@strongify(self);

self.resultLabel.text = [NSString stringWithFormat:@"结果:%@",x];

[self.resultLabel sizeToFit];

}];

- (void)onTextFieldChanged:(UITextField *)textField forEvent:(UIEvent *)event

{

if (textField.tag == 1000) {

NSLog(@"Result------->%@",self.resultLabel.text);

self.myTextFieldString1 = textField.text;

NSLog(@"Result------->%@",self.resultLabel.text);

}

if (textField.tag == 1001) {

self.myTextFieldString2 = textField.text;

}

if (textField.tag == 1002) {

self.myTextFieldString3 = textField.text;

}

}

我们在此分别为三个输入框的值构造了三个signal,任何一个信号的“变化”,都会带来result的变化。执行的结果如下:

2017-12-03 19:50:11.684640+0800 MDProject[44355:1633745] Result------->结果:6

2017-12-03 19:50:11.685474+0800 MDProject[44355:1633745] Result------->结果:15

我们看到, self.myTextFieldString1 = textField.text;(a发生变化)之后,结果就立即发生了变化。

三、RAC信号的实现原理

前面我已经由浅入深地介绍了signal的概念,我们知道,signal是用来传递的,既然有了传递的概念,那么就会有信号的发送者(信号的create),和接受者(信号的订阅)。前面的例子中我们知道,a、b、c的变化产生信号,订阅者拿到变化,刷新了UI;下面演示一下,一个信号,从产生到结束的详细过程,代码如下:

- (void)coldSignalTest

{

//信号的创建

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {

[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{

[subscriber sendNext:@1];//信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:10 schedule:^{

[subscriber sendNext:@2];//信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{

[subscriber sendNext:@3];//信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{

[subscriber sendCompleted];//信号的取消订阅

}];

return nil;

}];

//信号的订阅者1

[signal subscribeNext:^(id x) {

NSLog(@"Subscriber 1 recveive: %@", x);

}];

//信号的订阅者2

[[RACScheduler mainThreadScheduler] afterDelay:2.0 schedule:^{

[signal subscribeNext:^(id x) {

NSLog(@"Subscriber 2 recveive: %@", x);

}];

}];

}

从上面的代码注释中,可以看出信号的生命周期,包括分为信号的创建、信号的订阅、信号的发送、信号的订阅的取消。

1、信号的创建

+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {

return [RACDynamicSignal createSignal:didSubscribe];

}

这里createSignal:创建了一个RACDynamicSignal的信号,并将发送信号部分的block(didSubscribe)传了进去,我们再往下看:

+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {

RACDynamicSignal *signal = [[self alloc] init];

signal->_didSubscribe = [didSubscribe copy];

return [signal setNameWithFormat:@"+createSignal:"];

}

在这里我们可以看到,有个copy操作,将didSubscribe这个临时栈block放到了内存中保存。

2、信号的订阅

我们看下订阅信号的源码:

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {

NSCParameterAssert(nextBlock != NULL);

RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];

return [self subscribe:o];

}

这里将订阅者block封装成了一个RACSubscriber,我们看下这步封装,如下:

+ (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;

}

订阅者block被复制给RACSubscriber的_next,这个_next后面还有介绍。

我们再执行subscribe操作,进行订阅操作,由于RACDynamicSignal是RACSignal的子类,所以,这部操作的代码在RACDynamicSignal中,如下:

- (RACDisposable *)subscribe:(id)subscriber

{

NSCParameterAssert(subscriber != nil);

RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

if (self.didSubscribe != NULL) {

RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{

RACDisposable *innerDisposable =self.didSubscribe(subscriber);

[disposable addDisposable:innerDisposable];

}];

[disposable addDisposable:schedulingDisposable];

}

return disposable;

}

这段代码比较抽象,但是我们可以看出,中间被订阅者self.didSubscribe被执行了,这就是(1)中保存的_didSubscribe。

3、信号的发送

信号的发送源码,如下:

- (void)sendNext:(id)value {

@synchronized (self) {

void (^nextBlock)(id) = [self.next copy];

if (nextBlock == nil) return;

nextBlock(value);

}

}

信号的发送就是执行了相应的block(value),而这个self.next正是前面保存的订阅者的_next部分,由此我们可以看出,信号的传递本身是一个抽象概念,订阅者和被订阅者之间的绑定、以及信号的发送,都是通过执行共同的block(_didSubscribe和_next)来完成的。

4、信号的取消订阅

我们前面(2)可以看到信号的订阅时,订阅者block被封装到相应的RACDisposable中,当我们执行[subscriber sendCompleted]操作时,其中也就执行以下代码:

- (void)dispose {

.......

#if RACCompoundDisposableInlineCount

for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {

inlineCopy[i] = _inlineDisposables[i];

_inlineDisposables[i] = nil;

}

#endif

......

}

在前面信号的订阅时,订阅者block被封装成disposable,然后添加到disposable数组,而订阅者的取消,也是数组被置为nil的操作,然后被系统dealloc的过程。

四、冷信号与热信号

1、冷热信号的引入

在讲解这两个概念之前,我要先通过一个简单粗暴的例子,来便于这两个概念的理解。

2017年7月的某一天,初中生小明为了提升作文水平,选择订阅《读者》,这时他有两个选择,商家A提供的选择是,如果订了他家的《读者》,可以从第二月开始(2017年8月)每月都收到一份最新的《读者》,这样他全年可以收到5份《读者》。商家B提供的选择是,他会从2017年8月开始每月依次发给你2017年1月、2月、3月...4月的报纸,也就是说8月开始小明其实收到的是1月份的旧《读者》,不过这样他最终可以把全年的12份《读者》全部都收到。

好了,有个这个概念的铺垫,我们下面直接看示例代码:

- (void)coldSignalTest

{

//信号的创建

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {

[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{

[subscriber sendNext:@"一月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{

[subscriber sendNext:@"二月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{

[subscriber sendNext:@"三月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{

[subscriber sendNext:@"四月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{

[subscriber sendNext:@"五月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:6 schedule:^{

[subscriber sendNext:@"六月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:7 schedule:^{

[subscriber sendNext:@"七月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:8 schedule:^{

[subscriber sendNext:@"八月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:9 schedule:^{

[subscriber sendNext:@"九月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:10 schedule:^{

[subscriber sendNext:@"十月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:11 schedule:^{

[subscriber sendNext:@"十一月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:12 schedule:^{

[subscriber sendNext:@"十二月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:13 schedule:^{

[subscriber sendCompleted]; //信号的取消订阅

}];

return nil;

}];

//信号的订阅者

[[RACScheduler mainThreadScheduler] afterDelay:7.0 schedule:^{

[signal subscribeNext:^(id x) {

NSLog(@"小明 recveive: %@", x);

}];

}];

}

在此我们通过延迟时间来模仿,小明收到的每月的读者,打印结果如下:

2017-12-0419:12:35.517553+0800 MDProject[81121:3596317] Signal was created.

2017-12-0419:12:43.528421+0800 MDProject[81121:3596317] 小明 recveive: 一月份的《读者》

2017-12-0419:12:44.525774+0800 MDProject[81121:3596317] 小明 recveive: 二月份的《读者》

2017-12-0419:12:45.528010+0800 MDProject[81121:3596317] 小明 recveive: 三月份的《读者》

2017-12-0419:12:46.527258+0800 MDProject[81121:3596317] 小明 recveive: 四月份的《读者》

2017-12-0419:12:47.525560+0800 MDProject[81121:3596317] 小明 recveive: 五月份的《读者》

2017-12-0419:12:48.525488+0800 MDProject[81121:3596317] 小明 recveive: 六月份的《读者》

2017-12-0419:12:49.527300+0800 MDProject[81121:3596317] 小明 recveive: 七月份的《读者》

2017-12-0419:12:50.528087+0800 MDProject[81121:3596317] 小明 recveive: 八月份的《读者》

2017-12-0419:12:51.528652+0800 MDProject[81121:3596317] 小明 recveive: 九月份的《读者》

2017-12-0419:12:52.528625+0800 MDProject[81121:3596317] 小明 recveive: 十月份的《读者》

2017-12-0419:12:53.526341+0800 MDProject[81121:3596317] 小明 recveive: 十一月份的《读者》

2017-12-0419:12:55.529068+0800 MDProject[81121:3596317] 小明 recveive: 十二月份的《读者》

我们将上面的代码改编后,如下:

- (void)hotSignalTest

{

RACSubject *signal = [RACSubject subject];

[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{

[signal sendNext:@"一月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{

[signal sendNext:@"二月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{

[signal sendNext:@"三月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{

[signal sendNext:@"四月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{

[signal sendNext:@"五月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:6 schedule:^{

[signal sendNext:@"六月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:7 schedule:^{

[signal sendNext:@"七月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:8 schedule:^{

[signal sendNext:@"八月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:9 schedule:^{

[signal sendNext:@"九月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:10 schedule:^{

[signal sendNext:@"十月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:11 schedule:^{

[signal sendNext:@"十一月份的《读者》"]; //信号的发送

}];

[[RACScheduler mainThreadScheduler] afterDelay:12 schedule:^{

[signal sendNext:@"十二月份的《读者》"]; //信号的发送

}];

NSLog(@"Signal was created.");

[[RACScheduler mainThreadScheduler] afterDelay:7.0 schedule:^{

[signal subscribeNext:^(id x) {

NSLog(@"小明 recveive: %@", x);

}];

}];

}

打印结果如下:

2017-12-04 19:16:18.491057+0800 MDProject[81340:3606937] Signal was created.

2017-12-04 19:16:26.491986+0800 MDProject[81340:3606937] 小明 recveive: 八月份的《读者》

2017-12-04 19:16:27.490074+0800 MDProject[81340:3606937] 小明 recveive: 九月份的《读者》

2017-12-04 19:16:28.490190+0800 MDProject[81340:3606937] 小明 recveive: 十月份的《读者》

2017-12-04 19:16:29.490196+0800 MDProject[81340:3606937] 小明 recveive: 十一月份的《读者》

2017-12-04 19:16:31.490209+0800 MDProject[81340:3606937] 小明 recveive: 十二月份的《读者》

以上两个例子对比可以看出,商家A提供的是一种“热信号”,而商家B提供的是一种“冷信号”。

2、热信号实现原理

前面的例子中可知,RACSubject相关的操作会产生热信号。我先来看一下热信号的产生过程:

(1)RACSubject的初始化

RACSubject *signal = [RACSubject subject];

这一步的源码如下:

- (id)init {

self = [super init];

if (self == nil) return nil;

_disposable = [RACCompoundDisposable compoundDisposable];

_subscribers= [[NSMutableArray alloc] initWithCapacity:1];

return self;

}

这一步初始化了一个_subscribers数组,看名字就该知道,这是用来存放订阅者的数组。

(2)订阅者初始化

我们单步跟踪进去,看一下:

- (RACDisposable *)subscribe:(id)subscriber {

NSCParameterAssert(subscriber != nil);

RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

NSMutableArray *subscribers = self.subscribers;

@synchronized (subscribers) {

[subscribers addObject:subscriber];

}

return [RACDisposable disposableWithBlock:^{

@synchronized (subscribers) {

// Since newer subscribers are generally shorter-lived, search

// starting from the end of the list.

NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id obj, NSUInteger index, BOOL *stop) {

return obj == subscriber;

}];

if (index != NSNotFound) [subscribers removeObjectAtIndex:index];

}

}];

}

我们先留意下关键代码,可以看到,订阅者代码被加到了,1)中的订阅者数组中。

(3)信号的发送

我们看下RACSubject的SendNext方法:

- (void)sendNext:(id)value {

[self enumerateSubscribersUsingBlock:^(id subscriber) {

[subscriber sendNext:value];

}];

}

- (void)enumerateSubscribersUsingBlock:(void (^)(id subscriber))block {

NSArray *subscribers;

@synchronized (self.subscribers) {

subscribers = [self.subscribers copy];

}

for (id subscriber in subscribers) {

block(subscriber);

}

}

以上代码不难看出,sendNext这一步会遍历订阅者数组,依次执行每个订阅者的block方法。

3、冷信号和热信号的区别

有了上面的例子,冷信号与热信号的区别,其实很明显了:

1)冷信号是一对一的,订阅者与被订阅者通过block绑定,没有订阅者,就没有被订阅者,有了订阅者代码执行,被订阅者代码就会被完整地执行,所以冷信号给我们带来的是完整信号。

2)热信号的概念类似于Notification的概念,它不管某一个订阅者代码是否被执行,它都会去遍历订阅者数组,依次执行被订阅者代码,如果你先到,你就可以“先拿到”较早的信号,如果你晚加入订阅者数组,你就只能被晚点遍历到,接收到较晚的信号。

本文来自企鹅号 - 全球大搜罗媒体

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文来自企鹅号 - 全球大搜罗媒体

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档