Reactive Cocoa

Functional Reactive Programming

Functional Reactive Programming(函数响应式(反应式)编程,以下简称FRP)是一种响应变化的编程范式,它能通过一种信号机制来记录值的变化,信号可以被叠加、分割或合并,通过对信号的组合,就可以对值进行监听,有点像数学中的各种公式。例如:

f(x) = x^2 + 2x + 1;

每当x变化时,f(x)的值也随之变化,可以看出,这个函数是由一个基本函数构成的,即:

f(x) = f1(x)^2;
f1(x) = x + 1;

数学中有个结论,大致的意思就是大部分的函数,无论多复杂,都能由可列个基本函数构成。简单说来,计算f(x)的过程就是先计算f1(x)的值,再带入f(x)中计算,这个计算过程就是FRP中的核心之一,事件流(Stream)。每当x发生变化时,f(x)的值理应响应这种变化,这是FRP的另外一个核心:属性(Properties)。

ReactiveCocoa

ReactiveCocoa是Github的一个开源的项目,是IOS平台上对FRP的实现。

RACStream

RACStream就对应FRP中的事件流,一个RACStream对象的意义相当于上面的f1(x)(每一个基本函数都有一个与之对应的RACStream对象),最后组合出来的函数f(x)则由RACStreamComponent表示。一个RACStream对象应有几个基本要素:

  1. 传入值
  2. 返回值
  3. 如何与其他函数组合
  4. 如何确定函数的作用域
  5. 函数名
在Objective C中,所有的对象都可以用id表示,多个值的组合可以用RACTurple(可以吧多个值压包和解包),1、2搞定;
 
函数名就叫RACStream对象的name就行,5解决;
 
由于两个函数组合之后还是一个函数,故两个RACStream对象组合后是一个新生成的Stream对象,它的输入是第一个Stream的输入值(或两个Stream的输入),返回值是第二个Stream运算后得到的值(或两个Stream的输出),RACStream中有两个函数,concat:和zipWith:能够将两个Stream对象组合起来,concat是将两个Stream串行连接起来,第二个Stream的输入值为第一个Stream的输出值(也就是说,只有当第一个Stream执行完才能执行第二个Stream)。zipWith是将两个Stream组合起来,当两个Stream都产生输出值时,组合成的Stream才输出值(RACTuple),其中任何一个Stream无法输出,组合成的Stream都无法输出,3解决;
 
RACStream中有一个bind:方法,是用来监测值和控制运行状态的。
 

RACSignal

信号,ReactiveCocoa中的核心,是一个主动信号流,它表示未来将要发送的数据,也可以将它当做一个“事件转发器”。一个signal和一个数据源绑定,当数据源的数据有更新时,signal会向signal所有的subscriber(订阅者)发送事件。

Signal只会向subscriber发送三种类型的事件:

    • Next:接受下一个事件
    • Error:事件发生错误,无法继续接受
    • Completed:完成接受,(textField中的值不会再改变或是用户不希望继续接受新的值)

一个Signal的生命周期内可以接受任意多个Next事件,一个Error事件或者是Completed事件(两种事件只可能出现一种)。

 

最有意思的是,Signals之间能够进行各种组合,具体说来,它可以被修改(Map)、过滤(Filter)、叠加(Combine)和串联(Chain)。

Map

Filter

1 [[self.textField.rac_textSignal filter:^BOOL(NSString*value) {    

2     return [value length]>= 3; 3 }] subscribeNext:^(NSString*value) { 4 NSLog(@"Text field has been updated: %@", value); 5 >}]; 

textField的text改变时发出的signal会先被进行过滤,只有当text的长度大于等于3时,才会被发到下一个接收方继续执行。

Combine

1 RAC(self,login.enabled) =

2         [RACSignal combineLatest:@[_userName.rac_textSignal,

3                                    _password.rac_textSignal]

4                           reduce:^(NSString *userName, NSString *password){

5                                return @(userName.length > 0 && password.length > 0);

6                            }];

 

userName和password的text改变的signal被联合在了一起,只有当两个text的长度都大于0时,login按钮的enable才为YES。 

 

RACSubscriber

subscriber(订阅者),接受signal发出的事件(next,complete,error)

@weakify(self);

    RACSignal *aSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber){

        @strongify(self);

        [self doSomethingWithSuccessHandler:^{

            [subscriber sendNext:nil];

        }];

        return nil;

    }];

 

[aSignal subscribeNext:^(id x){

        NSLog(@"next event sended");

    }];

  

 

@weakify和@strongify的成对使用是确保在block中引用self不会引起循环引用的问题,这是RAC中定义的宏。

creatSignal函数创建了一个Signal,并定义了每次signal被订阅时需要做的事:doSomethingWithSuccessHandler:,这是一个异步的操作,当操作成功完成时,执行[subscriber sendNext:];告诉每一个订阅者,执行下一步操作。

注意,如果没有下面一句[aSignal subscribeNext:],则aSignal只会被创建,但是不执行block中的内容(doSomethingWithSuccessHandler:),这时signal没有一个订阅者,一般称之为冷信号(Cold)。而只有当一个Signal被订阅了以后([aSignal subscribeNext:]),才会执行block中的内容,这时signal有了一个订阅者,它会变成热信号(Hot),会执行creatSignal:后的block。当block中的内容执行到[subsriber sendNext:]时,所有的subscriber就会执行下一步的操作([aSignal subscribeNext:]后的block)。

副作用,一个信号(RACSignal)每添加一个订阅者(有一个对象执行[aSignal subscribeNext:]),creatSignal后的block就会被执行一次,这或许是你想要的,或许不是,可以写成[[RACSignal creatSignal:...] replay];避免多次执行creatSignal的block

 

RACScheduler

RACScheduler是ReactiveCocoa中对线程的简单封装,事件可以在指定的scheduler上分发执行,默认情况是事件都在一个默认的后台线程里面执行,如遇特殊情况需要在主线程调用,使用 deliverOn:可切换线程。

 

 

 

(未完待续...)

你可能感兴趣的:(Active)