• 函数响应式编程(FRP)框架--ReactiveCocoa


       由于工作原因,有段时间没更新博客了,甚是抱歉,只是,从今天開始我又活跃起来了,哈哈,于是决定每周更新一博。大家互相学习。交流。

      今天呢。讨论一下关于ReactiveCocoa,这个採用函数响应式编程(FRP)的框架,下面会对ReactiveCocoa简称为RAC。

      之前看过一遍文章,说的是作为一个iOS开发人员,写的每一行代码差点儿都是在对应某个事件,比如button的点击。收到网络消息,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。可是这些事件都用不同的方式来处理,比方action、delegate、KVO、callback等。

    我非常赞同这种说法,于是。即然这种话,那我们为什么不把这些须要响应的事件流统一放在一起呢。非常幸运的是,Github有一个开源项目,即 ReactiveCocoa简称RAC,就是基于响应式编程思想的Objective-C实践。

      何为RAC呢:RAC是为应用中发生的不同事件流提供了一个标准接口。我们能够使用一些基本工具来更easy的连接、过滤和组合。

      RAC结合了几种编程风格:

      函数式编程(Functional Programming):使用高阶函数。比如函数用其它函数作为參数。

      响应式编程(Reactive Programming):关注于数据流和变化传播。

      所以,你可能听说过ReactiveCocoa被描写叙述为函数响应式编程(FRP)框架。 


      有句比喻非常好非常形象的对RAC做了总结:

    能够把信号想象成水龙头,仅仅只是里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列。不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的。除非有了接收方(subscriber),才会打开。

    这样仅仅要有新的玻璃球进来,就会自己主动传送给接收方。

    能够在水龙头上加一个过滤嘴(filter)。不符合的不让通过,也能够加一个修改装置,把球改变成符合自己的需求(map)。

    也能够把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样仅仅要当中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。

    ” --  来自博文  http://blog.csdn.net/xdrt81y/article/details/30624469


      首先导入RAC框架:

        能够通过CocoaPods导入RAC框架:

         platform:ios,'9.0'

        use_frameworks! #导入才不会报错

        pod'ReactiveCocoa'

      RAC为应用中发生的不同事件流提供了一个标准接口。

    在ReactiveCocoa术语中这个叫做信号(signal),由RACSignal类表示。

      ReactiveCocoa signal(RACSignal)发送事件流给它的subscriber(订阅者)。

    眼下总共同拥有三种类型的事件:next、error、completed。一个signal在因error终止或者完毕前,能够发送随意数量的next事件。RACSignal有非常多方法能够来订阅不同的事件类型。每一个方法都须要至少一个block。当事件发生时就会运行block中的逻辑。

      ReactiveCocoa框架使用category来为非常多基本UIKit控件加入signal。这样就能给控件加入订阅了,text field的rac_textSignal就是这么来的。

      看个样例:

           RACSignal *usernameSourceSignal =self.usernameTextField.rac_textSignal;

          RACSignal *filteredUsername = [usernameSourceSignalfilter:^BOOL(id value) {

           NSString *string = value;

           return string.length >3;   }];

            [filteredUsernamesubscribeNext:^(id x) {

           NSLog(@"%@",x);  }];

       在这里。rac_textSignal是起始事件。然后数据通过一个filter。假设这个事件包括一个长度超过3的字符串,那么该事件就能够通过。

    管道的最后一步就  是subscribeNext:block在这里打印出事件的值。

    filter操作的输出也是RACSignal。

       事实上。RACSignal的每一个操作都会返回一个RACsignal,这在术语上叫做连贯接口(fluent interface)。这个功能能够让你直接构建管道。而不用每一步都使用本地变量。

      于是将上面的样例。能够变成:

          [[self.usernameTextField.rac_textSignalfilter:^BOOL(NSString* string) {

           return  string.length >3 ;

           }]subscribeNext:^(id x) { 

              NSLog(@"*** %@",x);   

          }];


    类型转换:

        我们能够使用map(转换)操作通过block改变事件的数据。

    map从上一个next事件接收数据。通过运行block把返回值传给下一个next事件。在上面的代码中,mapNSString为输入,取字符串的长度,返回一个NSNumber。

    举个栗子:

      [[[self.usernameTextField.rac_textSignalmap:^id(NSString *text) {

            return@(text.length);

         }]filter:^BOOL(NSNumber *length ) {

            return  [lengthintegerValue];

         }]subscribeNext:^(id x) {

            NSLog(@"map___%@",x);

         }];


     能看到map操作之后的步骤收到的都是NSNumber实例。你能够使用map操作来把接收的数据转换成想要的类型。仅仅要它是个对象。


    创建有效状态信号:

         首先要做的就是创建一些信号,比方常见的登录界面,来表示username和password输入框中的输入内容是否有效,如:

          RACSignal *validUsernameSignal = [self.usernameTextField.rac_textSignalmap:^id(NSString *text) {

           return@([selfisValidUsername:text]);

        }];

       RACSignal *validPasswordSignal = [self.passwordTextField.rac_textSignalmap:^id(id value) {

           NSString *text = value;

           return@([selfisValidPassword:text]);   

        }];


       我们能够转换这些信号,从而能为输入框设置不同的背景颜色。

    基本上就是,你订阅这些信号,然后用接收到的值来更新输入框的背景颜色。

      使用RAC 

         RAC宏同意直接把信号的输出应用到对象的属性上。

    RAC宏有两个參数,第一个是须要设置属性值的对象,第二个是属性名。

    每次信号产生一个next事件,传递过来的值都会应用到该属性上。

         

     RAC(self.passwordTextField,backgroundColor) = [validPasswordSignalmap:^id(NSNumber *passwordValid) {

           NSLog(@"%d",[passwordValidboolValue]);

           return [passwordValidboolValue]?[UIColorclearColor]:[UIColoryellowColor];

        }];

       RAC(self.usernameTextField,backgroundColor) = [validUsernameSignalmap:^id(NSNumber *usernameValid) {

           NSLog(@"%d",[usernameValidboolValue]);

           return [usernameValidboolValue]?[UIColorclearColor]:[UIColoryellowColor];

        }];


    信号聚合

      使用combineLatest:reduce:方法把几个信号RACSignal产生的最新的值聚合在一起,并生成一个新的信号。

    每次这两个源信号的不论什么一个产生新值时。reduce block都会运行,block的返回值会发给下一个信号。

        RACSignal *signUpActiveSignal = [RACSignalcombineLatest:@[validUsernameSignal,validPasswordSignal]reduce:^id(NSNumber *usernameValid,NSNumber *passwordValid){

           return@([usernameValidboolValue]&&[passwordValidboolValue]);

        }];

        [signUpActiveSignalsubscribeNext:^(NSNumber *signupActive) {

           self.signInButton.enabled = [signupActiveboolValue];

        }];

    ReactiveCocoa处理button的事件 

        须要用到ReactiveCocoaUIKit加入的还有一个方法,rac_signalForControlEvents。

    信号中的信号

       使用map方法,把button点击信号转换成了登录信号。

    subscriber输出log,把map操作改成flattenMap,这个操作把button点击事件转换为登录信号,同一时候还从内部信号发送事件到外部信号。


      doNext:是直接跟在button点击事件的后面。并且doNext: block并没有返回值。

    由于它是附加操作,并不改变事件本身。

      

        [[[[self.signInButton

           rac_signalForControlEvents:UIControlEventTouchUpInside]

          doNext:^(id x){

              self.signInButton.enabled =NO;

              self.signInFailureText.hidden =YES;

           }]

         flattenMap:^id(id x){

             return[selfsignInSignal];

          }]

        subscribeNext:^(NSNumber*signedIn){

            self.signInButton.enabled =YES;

            BOOL success =[signedInboolValue];

            self.signInFailureText.hidden = success;

            if(success){

                 [selfperformSegueWithIdentifier:@"signInSuccess"sender:self];

             }

         }];


    创建信号

         使用RACSignalcreateSignal:方法来创建信号。

    方法的參数是一个block,这个block描写叙述了这个信号。当这个信号有subscriber时。block里的代码就会运行。

     block的參数是一个subscriber实例,它遵循RACSubscriber协议。协议里有一些方法来产生事件,你能够发送随意数量的next事件,或者用errorcomplete事件来终止。本例中,信号发送了一个next事件来表示登录是否成功。随后是一个complete事件。 

     这个block的返回值是一个RACDisposable对象。它同意你在一个订阅被取消时运行一些清理工作。当前的信号不须要运行清理操作,所以返回nil就能够了。

    - (RACSignal *)signInSignal {

       return [RACSignalcreateSignal:^RACDisposable *(id subscriber){

            [self.signInService

            signInWithUsername:self.usernameTextField.text

            password:self.passwordTextField.text

            complete:^(BOOL success){

                 [subscribersendNext:@(success)];

                 [subscribersendCompleted];

             }];

           returnnil;

        }];

    }


    总结:

           ReactiveCocoa的核心就是信号,而它只是就是事件流,ReactiveCocoa的主旨是让你的代码更简洁易懂。这值得多想想。


    这里将附上demo。欢迎大家阅读。









  • 相关阅读:
    BZOJ 4316: 小C的独立集 (仙人掌,树形DP)
    LOJ #2587. 「APIO2018」铁人两项 (圆方树,树形DP)
    BZOJ 5329: [Sdoi2018]战略游戏 (圆方树,树链的并)
    CF487E Tourists (圆方树,LCT)
    BZOJ 4873: [Shoi2017]寿司餐厅 最大权闭合图
    【转】python文件打开方式详解——a、a+、r+、w+区别
    【转】使用git将项目上传到github(最简单方法)
    整数型数组组合成字符串
    【转】浏览器中输入url后发生了什么
    去除列表中重复的元素
  • 原文地址:https://www.cnblogs.com/yxysuanfa/p/7348966.html
Copyright © 2020-2023  润新知