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

*****阅读完此文,大概需要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的概念,它不管某一个订阅者代码是否被执行,它都会去遍历订阅者数组,依次执行被订阅者代码,如果你先到,你就可以“先拿到”较早的信号,如果你晚加入订阅者数组,你就只能被晚点遍历到,接收到较晚的信号。

  • 发表于:
  • 原文链接:http://kuaibao.qq.com/s/20171208G0VJ9M00?refer=cp_1026

扫码关注云+社区