最快让你上手ReactiveCocoa之基础篇

作者: 袁峥 | 来源:发表于2015-09-25 23:23 被阅读198215次

    前言

    很多blog都说ReactiveCocoa好用,然后各种秀自己如何灵活运用ReactiveCocoa,但是感觉真正缺少的是一篇如何学习ReactiveCocoa的文章,小编看了很多篇都没看出怎么使用ReactiveCocoa,于是决定自己写一遍关于学习ReactiveCocoa的文章,本文主要针对如何从零开始学习ReactiveCocoa,这里非常感谢3个人(支点的雷纯锋,camera360的宋潘,以及我的小学弟何宗柱(我爱科技)),在我研究ReactiveCocoa对我的帮助。
    如果喜欢我的文章,可以关注我,微博:袁峥Seemygo之后还会更新《最快让你上手ReactiveCocoa之进阶篇》

    1.ReactiveCocoa简介

    ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。

    2.ReactiveCocoa作用

    • 在我们iOS开发过程中,当某些事件响应的时候,需要处理某些业务逻辑,这些事件都用不同的方式来处理。
    • 比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式。
    • 其实这些事件,都可以通过RAC处理
    • ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。

    3.编程思想

    在开发中我们也不能太依赖于某个框架,否则这个框架不更新了,导致项目后期没办法维护,比如之前Facebook提供的Three20框架,在当时也是神器,但是后来不更新了,也就没什么人用了。因此我感觉学习一个框架,还是有必要了解它的编程思想

    先简单介绍下目前咱们已知的编程思想

    3.1 面向过程:处理事情以过程为核心,一步一步的实现。

    3.2 面向对象:万物皆对象

    3.3 链式编程思想:是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3)

    • 链式编程特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)

    • 代表:masonry框架。

    • 模仿masonry,写一个加法计算器,练习链式编程思想。

    Snip20150925_2.png Snip20151225_4.png Snip20150925_1.png Snip20151225_5.png Paste_Image.png
    3.4 响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。
    • 代表:KVO运用。

    3.5 函数式编程思想:是把操作尽量写成一系列嵌套的函数或者方法调用。

    • 函数式编程特点:每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)

    • 代表:ReactiveCocoa。

    • 用函数式编程实现,写一个加法计算器,并且加法计算器自带判断是否等于某个值.

    Paste_Image.png Paste_Image.png

    4.ReactiveCocoa编程思想

    ReactiveCocoa结合了几种编程风格:

    函数式编程(Functional Programming)

    响应式编程(Reactive Programming)

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

    以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。

    5.如何导入ReactiveCocoa框架

    通常都会使用CocoaPods(用于管理第三方框架的插件)帮助我们导入。

    PS:CocoaPods教程

    注意

    • podfile如果只描述pod 'ReactiveCocoa', '~> 4.0.2-alpha-1',会导入不成功。


      Snip20150926_1.png
    • 报错提示信息

    Snip20150926_2.png
    • 需要在podfile加上use_frameworks,重新pod install 才能导入成功。
    Snip20150926_3.png

    6.ReactiveCocoa常见类。

    学习框架首要之处:个人认为先要搞清楚框架中常用的类,在RAC中最核心的类RACSiganl,搞定这个类就能用ReactiveCocoa开发了。

    6.1RACSiganl:信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。

    注意

    • 信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。

    • 默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。

    • 如何订阅信号:调用信号RACSignal的subscribeNext就能订阅。

    • RACSiganl简单使用:

        // RACSignal使用步骤:
        // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
        // 2.订阅信号,才会激活信号. - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
        // 3.发送信号 - (void)sendNext:(id)value
        
        
        // RACSignal底层实现:
        // 1.创建信号,首先把didSubscribe保存到信号中,还不会触发。
        // 2.当信号被订阅,也就是调用signal的subscribeNext:nextBlock
        // 2.2 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
        // 2.1 subscribeNext内部会调用siganl的didSubscribe
        // 3.siganl的didSubscribe中调用[subscriber sendNext:@1];
        // 3.1 sendNext底层其实就是执行subscriber的nextBlock
        
        // 1.创建信号
        RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            // block调用时刻:每当有订阅者订阅信号,就会调用block。
            
            // 2.发送信号
            [subscriber sendNext:@1];
            
            // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
            [subscriber sendCompleted];
     
            return [RACDisposable disposableWithBlock:^{
                
                // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
                
                // 执行完Block后,当前信号就不在被订阅了。
                
                NSLog(@"信号被销毁");
                
            }];
        }];
        
        // 3.订阅信号,才会激活信号.
        [siganl subscribeNext:^(id x) {
            // block调用时刻:每当有信号发出数据,就会调用block.
            NSLog(@"接收到数据:%@",x);
        }];
    
    

    6.2 RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。

    6.3 RACDisposable:用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。

    • 使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。

    6.4 RACSubject:RACSubject:信号提供者,自己可以充当信号,又能发送信号。

    • 使用场景:通常用来代替代理,有了它,就不必要定义代理了。

    RACReplaySubject:重复提供信号类,RACSubject的子类。

    • RACReplaySubjectRACSubject区别:

      • RACReplaySubject可以先发送信号,在订阅信号,RACSubject就不可以。
    • 使用场景一:如果一个信号每被订阅一次,就需要把之前的值重复发送一遍,使用重复提供信号类。

    • 使用场景二:可以设置capacity数量来限制缓存的value的数量,即只缓充最新的几个值。

    • RACSubject和RACReplaySubject简单使用:

        // RACSubject使用步骤
        // 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
        // 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
        // 3.发送信号 sendNext:(id)value
    
        // RACSubject:底层实现和RACSignal不一样。
        // 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
        // 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
    
        // 1.创建信号
        RACSubject *subject = [RACSubject subject];
    
        // 2.订阅信号
        [subject subscribeNext:^(id x) {
            // block调用时刻:当信号发出新值,就会调用.
            NSLog(@"第一个订阅者%@",x);
        }];
        [subject subscribeNext:^(id x) {
            // block调用时刻:当信号发出新值,就会调用.
            NSLog(@"第二个订阅者%@",x);
        }];
    
        // 3.发送信号
        [subject sendNext:@"1"];
    
    
        // RACReplaySubject使用步骤:
        // 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
        // 2.可以先订阅信号,也可以先发送信号。
        // 2.1 订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
        // 2.2 发送信号 sendNext:(id)value
    
        // RACReplaySubject:底层实现和RACSubject不一样。
        // 1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
        // 2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock
    
        // 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。
        // 也就是先保存值,在订阅值。
    
        // 1.创建信号
        RACReplaySubject *replaySubject = [RACReplaySubject subject];
    
        // 2.发送信号
        [replaySubject sendNext:@1];
        [replaySubject sendNext:@2];
    
        // 3.订阅信号
        [replaySubject subscribeNext:^(id x) {
    
            NSLog(@"第一个订阅者接收到的数据%@",x);
        }];
    
        // 订阅信号
        [replaySubject subscribeNext:^(id x) {
    
            NSLog(@"第二个订阅者接收到的数据%@",x);
        }];
    
    
    • RACSubject替换代理
        // 需求:
        // 1.给当前控制器添加一个按钮,modal到另一个控制器界面
        // 2.另一个控制器view中有个按钮,点击按钮,通知当前控制器
        
    步骤一:在第二个控制器.h,添加一个RACSubject代替代理。
    @interface TwoViewController : UIViewController
    
    @property (nonatomic, strong) RACSubject *delegateSignal;
    
    @end
    
    步骤二:监听第二个控制器按钮点击
    @implementation TwoViewController
    - (IBAction)notice:(id)sender {
        // 通知第一个控制器,告诉它,按钮被点了
        
         // 通知代理
         // 判断代理信号是否有值
        if (self.delegateSignal) {
            // 有值,才需要通知
            [self.delegateSignal sendNext:nil];
        }
    }
    @end
    
    步骤三:在第一个控制器中,监听跳转按钮,给第二个控制器的代理信号赋值,并且监听.
    @implementation OneViewController 
    - (IBAction)btnClick:(id)sender {
        
        // 创建第二个控制器
        TwoViewController *twoVc = [[TwoViewController alloc] init];
        
        // 设置代理信号
        twoVc.delegateSignal = [RACSubject subject];
        
        // 订阅代理信号
        [twoVc.delegateSignal subscribeNext:^(id x) {
           
            NSLog(@"点击了通知按钮");
        }];
        
        // 跳转到第二个控制器
        [self presentViewController:twoVc animated:YES completion:nil];
        
    }
    @end
        
    

    6.6RACTuple:元组类,类似NSArray,用来包装值.

    6.7RACSequence:RAC中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典。

    使用场景:1.字典转模型

    RACSequence和RACTuple简单使用

        // 1.遍历数组
        NSArray *numbers = @[@1,@2,@3,@4];
        
        // 这里其实是三步
        // 第一步: 把数组转换成集合RACSequence numbers.rac_sequence
        // 第二步: 把集合RACSequence转换RACSignal信号类,numbers.rac_sequence.signal
        // 第三步: 订阅信号,激活信号,会自动把集合中的所有值,遍历出来。
        [numbers.rac_sequence.signal subscribeNext:^(id x) {
           
            NSLog(@"%@",x);
        }];
        
        
        // 2.遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
        NSDictionary *dict = @{@"name":@"xmg",@"age":@18};
        [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
           
            // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
            RACTupleUnpack(NSString *key,NSString *value) = x;
            
            // 相当于以下写法
    //        NSString *key = x[0];
    //        NSString *value = x[1];
            
            NSLog(@"%@ %@",key,value);
            
        }];
        
        
        // 3.字典转模型
        // 3.1 OC写法
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
        
        NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
        
        NSMutableArray *items = [NSMutableArray array];
        
        for (NSDictionary *dict in dictArr) {
            FlagItem *item = [FlagItem flagWithDict:dict];
            [items addObject:item];
        }
        
        // 3.2 RAC写法
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
        
        NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
    
        NSMutableArray *flags = [NSMutableArray array];
        
        _flags = flags;
        
        // rac_sequence注意点:调用subscribeNext,并不会马上执行nextBlock,而是会等一会。
        [dictArr.rac_sequence.signal subscribeNext:^(id x) {
            // 运用RAC遍历字典,x:字典
            
            FlagItem *item = [FlagItem flagWithDict:x];
            
            [flags addObject:item];
            
        }];
        
        NSLog(@"%@",  NSStringFromCGRect([UIScreen mainScreen].bounds));
        
        
        // 3.3 RAC高级写法:
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
        
        NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
        // map:映射的意思,目的:把原始值value映射成一个新值
        // array: 把集合转换成数组
        // 底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。
        NSArray *flags = [[dictArr.rac_sequence map:^id(id value) {
         
            return [FlagItem flagWithDict:value];
            
        }] array];
    
    

    6.8RACCommand:RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。

    使用场景:监听按钮点击,网络请求

    RACCommand简单使用

     
        // 一、RACCommand使用步骤:
        // 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
        // 2.在signalBlock中,创建RACSignal,并且作为signalBlock的返回值
        // 3.执行命令 - (RACSignal *)execute:(id)input
        
        // 二、RACCommand使用注意:
        // 1.signalBlock必须要返回一个信号,不能传nil.
        // 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
        // 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
        // 4.RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。
        
        // 三、RACCommand设计思想:内部signalBlock为什么要返回一个信号,这个信号有什么用。
        // 1.在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
        // 2.当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。
        
        // 四、如何拿到RACCommand中返回信号发出的数据。
        // 1.RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
        // 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
        
        // 五、监听当前命令是否正在执行executing
        
        // 六、使用场景,监听按钮点击,网络请求
    
    
        // 1.创建命令
        RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
            
            
            NSLog(@"执行命令");
            
            // 创建空信号,必须返回信号
            //        return [RACSignal empty];
            
            // 2.创建信号,用来传递数据
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                
                [subscriber sendNext:@"请求数据"];
                
                // 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
                [subscriber sendCompleted];
                
                return nil;
            }];
            
        }];
        
        // 强引用命令,不要被销毁,否则接收不到数据
        _conmmand = command;
        
        
       
        // 3.订阅RACCommand中的信号
        [command.executionSignals subscribeNext:^(id x) {
            
            [x subscribeNext:^(id x) {
                
                NSLog(@"%@",x);
            }];
            
        }];
        
        // RAC高级用法
        // switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
        [command.executionSignals.switchToLatest subscribeNext:^(id x) {
            
            NSLog(@"%@",x);
        }];
        
        // 4.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。
        [[command.executing skip:1] subscribeNext:^(id x) {
            
            if ([x boolValue] == YES) {
                // 正在执行
                NSLog(@"正在执行");
                
            }else{
                // 执行完成
                NSLog(@"执行完成");
            }
            
        }];
       // 5.执行命令
        [self.conmmand execute:@1];
        
    
    

    6.9RACMulticastConnection:用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。

    使用注意:RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建.

    RACMulticastConnection简单使用:

        // RACMulticastConnection使用步骤:
        // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
        // 2.创建连接 RACMulticastConnection *connect = [signal publish];
        // 3.订阅信号,注意:订阅的不在是之前的信号,而是连接的信号。 [connect.signal subscribeNext:nextBlock]
        // 4.连接 [connect connect]
        
        // RACMulticastConnection底层原理:
        // 1.创建connect,connect.sourceSignal -> RACSignal(原始信号)  connect.signal -> RACSubject
        // 2.订阅connect.signal,会调用RACSubject的subscribeNext,创建订阅者,而且把订阅者保存起来,不会执行block。
        // 3.[connect connect]内部会订阅RACSignal(原始信号),并且订阅者是RACSubject
        // 3.1.订阅原始信号,就会调用原始信号中的didSubscribe
        // 3.2 didSubscribe,拿到订阅者调用sendNext,其实是调用RACSubject的sendNext
        // 4.RACSubject的sendNext,会遍历RACSubject所有订阅者发送信号。
        // 4.1 因为刚刚第二步,都是在订阅RACSubject,因此会拿到第二步所有的订阅者,调用他们的nextBlock
    
        
        // 需求:假设在一个信号中发送请求,每次订阅一次都会发送请求,这样就会导致多次请求。
        // 解决:使用RACMulticastConnection就能解决.
        
        // 1.创建请求信号
       RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            
            NSLog(@"发送请求");
         
            return nil;
        }];
        // 2.订阅信号
        [signal subscribeNext:^(id x) {
           
            NSLog(@"接收数据");
            
        }];
        // 2.订阅信号
        [signal subscribeNext:^(id x) {
            
            NSLog(@"接收数据");
            
        }];
        
        // 3.运行结果,会执行两遍发送请求,也就是每次订阅都会发送一次请求
        
        
        // RACMulticastConnection:解决重复请求问题
        // 1.创建信号
        RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            
            NSLog(@"发送请求");
            [subscriber sendNext:@1];
            
            return nil;
        }];
        
        // 2.创建连接
        RACMulticastConnection *connect = [signal publish];
        
        // 3.订阅信号,
        // 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
        [connect.signal subscribeNext:^(id x) {
           
            NSLog(@"订阅者一信号");
            
        }];
        
        [connect.signal subscribeNext:^(id x) {
            
            NSLog(@"订阅者二信号");
            
        }];
        
        // 4.连接,激活信号
        [connect connect];
        
    

    6.10 RACScheduler:RAC中的队列,用GCD封装的。

    6.11 RACUnit :表⽰stream不包含有意义的值,也就是看到这个,可以直接理解为nil.

    6.12 RACEvent: 把数据包装成信号事件(signal event)。它主要通过RACSignal的-materialize来使用,然并卵。

    7.ReactiveCocoa开发中常见用法。

    7.1 代替代理:

    • rac_signalForSelector:用于替代代理。

    7.2 代替KVO :

    • rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。

    7.3 监听事件:

    • rac_signalForControlEvents:用于监听某个事件。

    7.4 代替通知:

    • rac_addObserverForName:用于监听某个通知。

    7.5 监听文本框文字改变:

    • rac_textSignal:只要文本框发出改变就会发出这个信号。

    7.6 处理当界面有多次请求时,需要都获取到数据时,才能展示界面

    • rac_liftSelector:withSignalsFromArray:Signals:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法。
    • 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。

    7.7 代码演示

    
        // 1.代替代理
        // 需求:自定义redView,监听红色view中按钮点击
        // 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
        // rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
        // 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
        [[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
            NSLog(@"点击红色按钮");
        }];
    
        // 2.KVO
        // 把监听redV的center属性改变转换成信号,只要值改变就会发送信号
        // observer:可以传入nil
        [[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
    
            NSLog(@"%@",x);
    
        }];
    
        // 3.监听事件
        // 把按钮点击事件转换为信号,点击按钮,就会发送信号
        [[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    
            NSLog(@"按钮被点击了");
        }];
    
        // 4.代替通知
        // 把监听到的通知转换信号
        [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
            NSLog(@"键盘弹出");
        }];
    
        // 5.监听文本框的文字改变
       [_textField.rac_textSignal subscribeNext:^(id x) {
    
           NSLog(@"文字改变了%@",x);
       }];
       
       // 6.处理多个请求,都返回结果的时候,统一做处理.
        RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            // 发送请求1
            [subscriber sendNext:@"发送请求1"];
            return nil;
        }];
        
        RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            // 发送请求2
            [subscriber sendNext:@"发送请求2"];
            return nil;
        }];
        
        // 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
        [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
        
        
    }
    // 更新UI
    - (void)updateUIWithR1:(id)data r2:(id)data1
    {
        NSLog(@"更新UI%@  %@",data,data1);
    }
    
    

    8.ReactiveCocoa常见宏。

    8.1 RAC(TARGET, [KEYPATH, [NIL_VALUE]]):用于给某个对象的某个属性绑定。

        // 只要文本框文字改变,就会修改label的文字
        RAC(self.labelView,text) = _textField.rac_textSignal;
    
    

    8.2 RACObserve(self, name):监听某个对象的某个属性,返回的是信号。

    [RACObserve(self.view, center) subscribeNext:^(id x) {
          
            NSLog(@"%@",x);
        }];
    
    

    8.3 @weakify(Obj)和@strongify(Obj),一般两个都是配套使用,在主头文件(ReactiveCocoa.h)中并没有导入,需要自己手动导入,RACEXTScope.h才可以使用。但是每次导入都非常麻烦,只需要在主头文件自己导入就好了。

    8.4 RACTuplePack:把数据包装成RACTuple(元组类)

    // 把参数中的数据包装成元组
        RACTuple *tuple = RACTuplePack(@10,@20);
    

    8.5 RACTupleUnpack:把RACTuple(元组类)解包成对应的数据。

        // 把参数中的数据包装成元组
        RACTuple *tuple = RACTuplePack(@"xmg",@20);
        
        // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
        // name = @"xmg" age = @20
        RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
    

    联系方式

    如果你喜欢这篇文章,可以继续关注我,微博:吖了个峥,欢迎交流。

    相关文章

      网友评论

      • 14054f94f25f:RAC(TARGET, [KEYPATH, [NIL_VALUE]]):用于给某个对象的某个属性绑定
        14054f94f25f:这里跟例子不对照吧
      • MrZombie:写的很好,可是作者好粗心啊:joy:
      • 默认头:如果稍微接触过RAC,这篇文章还是不错的,如果作为新手,尤其没接触过响应式函数式编程的人,可能这个写作的角度不是很清晰
      • noname004:加减乘除的四个方法的.h文件类名是不是错了?应该是manager而不是maker吧?
      • Harely:我运行这行代码:[textField.rac_textSignal bind:^RACStreamBindBlock _Nonnull{}];
        时,会报错,错误为:Incompatible block pointer types sending 'RACStreamBindBlock _Nonnull (^)(void)' to parameter of type 'RACSignalBindBlock _Nonnull (^ _Nonnull)(void)'
        同时我也导入了:#import <ReactiveObjC/RACReturnSignal.h>,但是也会报错:'#import <ReactiveObjC/RACReturnSignal.h> filet not found',
        我导入的类库是:
        pod 'ReactiveObjC', '~> 3.1.0'
        pod 'ReactiveCocoa', '~> 7.0',有空能解决下吗?
      • shengshenger:楼主,模仿masonrydemo能分享一下么?看的不是很明白
      • AliceJordan:谢谢您
      • 杨涛170308:看不懂 这个到底有什么作用啊
      • MarkJY:xcode9.1在编译时完全没问题,在打包时提示找不到ReactiveCocoa,有大神知道怎么搞吗
      • StephenLau:代码问题太多了。:disappointed_relieved:
      • 19729bb337db:大神,您能把函数式编程思想的代码补全吗?很尴尬啊!!!
      • 桐丘:哥们,"RACSignal"写成了“ RACSiganl”。很好的教程,收藏加关注,点赞。
      • nuannuan_nuan:有个地方不是很明白:[[[c calculator:] equal] isEqual]的链式调用,calculator里的block调用返回一个result,equal的block调用时会接受一个result的参数,但是链式流是如何衔接的呢?将第一个calculator的返回值作为下一个equal的参数。这点不是很明白。希望博主解答下或者看下源码。
        iManuQiao:我也越看越迷糊了,方法本身没有实现的细节,所有的计算在block里。block没地方入参,出参也没人接。
        nuannuan_nuan:我猜测是不是这里的result是个全局变量,在调用equalblock的时候传入的。
      • 郝嗨森:写的很详细,多少明白了一些了,还要多看几遍多练习一下。我可以转发一下吗?
      • f2503bba4cfa:弱弱的问一句:链式编程和函数编程是同一个概念吗?
        海边的1984_:链式编程是指用"."来完成方法的调用,每次都返回一个对象继续调用下一个方法
        函数编程是指方法返回的是一个函数指针,在oc里面函数指针就是block这种形式,加上()就能调用。
      • 2e2ff4d99fc0:写的很好,支持一下
      • e2aa8501b499:Signal 写错了 写成Siganl了 。。我说怎么看着这么别扭
      • omni_Hsin:RACSubject替换代理 ,我觉得更像是block,☺️
        hahand:subject更像notification,保存一个selector数组管理订阅者回调
      • omni_Hsin:链式编程那块怎么是图片都,源码呢?
      • df4b2e9148d9:"block必须有返回值(本身对象),block参数(需要操作的值)"是什么意思呀?大神
      • stlndm:计算器乘以单词multi
      • Caiflower:终于看懂了...
      • 动物园园长熊熊酱:6.8 代码里面_conmmand 应该是_command吧 谢谢作者的讲解
      • MR_詹:峥老师,RACCommand你上面提到需要强引用,那么RACCommand是不是不能作为一个类方法的返回值使用?我在项目测试过,RACCommand作为一个类方法的返回值,订阅这个信号,发送信号,一直都没有返回?这需要怎么做,谢谢
      • 遛遛食:请问 RACCommand是做什么的,看了还是很迷茫
      • 木沐一: // 1.代替代理
        // 需求:自定义redView,监听红色view中按钮点击
        // 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
        // rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
        // 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
        [[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
        NSLog(@"点击红色按钮");
        }];

        这里你究竟是怎么监测到按钮的,完全无法理解这里......求帮助,头都大了
        木沐一:这里代替代理的例子都有问题,这根本从头到尾和代理没关系好么,连delegate都是没设的,和代替代理有啥关系,网上找的例子一看就明白了。
        [[self rac_signalForSelector:@selector(tableView:didSelectRowAtIndexPath:) fromProtocol:@protocol(UITableViewDelegate) ] subscribeNext:^(RACTuple * x) {
        NSLog(@"点击了");
        NSLog(@"%@,%@",x.first,x.second);
        }];
        tableview.delegate = self;
      • Coderrr://当满足条件,则改变按钮状态
        RAC(self.sureButton, enabled) = [RACSignal combineLatest:@[
        self.usernameTextField.rac_textSignal,
        self.priceTextField.rac_textSignal,
        self.orderNumTextField.rac_textSingnal
        ] reduce:^(NSString *username, NSString *price,NSString *orderNum) {
        return @(username.length > 0 && price.length > 0 && orderNum.length > 0);
        }];
      • 知忧丶:入门神器,带你屌丝变大神
      • ccnyou:哥,有个单词拼错了,Equal
      • b4067f4d3883:Use legacy swift language version找不到是什么情况
      • 志城:使用cocoapods导入时候需要注意的事项: 链接不是我的,是我遇到问题之后查找得到的答案。http://blog.csdn.net/ruglcc/article/details/55255006
      • lynch0571:已收录《千赞专题》,欢迎关注:http://www.jianshu.com/collection/032a478c3dbf
      • 时间已静止:“3.3 链式编程思想:是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3)”

        写成:"对象 = 对象.a(1).b(2).c(3)"是不是更好理解?

        还有是对 点 语法的默认使用

        对象.a :[对象 a]

      • 鬼谷门生:袁老师,像你说的开发中不能太依赖于某个框架,像AFN这样的库哪天不维护了我们可以替换,但像ReactiveCocoa这样的库提供的是一种编程思想,一种范式,以后项目大好像要替换还是很困难呀?
      • 资料库:大神你的那个use_frameworks在文中没有加感叹号。。。
      • 前进的火车2015:袁老师,我看了一周几个地方不明白希望指点一下:
        1.红色视图不走方法
        UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
        _button = button;
        button.frame = CGRectMake(10, 10, 80, 80);
        [_redView addSubview:button];
        button.backgroundColor = [UIColor whiteColor];
        [button setTitle:@"按钮" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
        [[_redView rac_signalForSelector:@selector(btnClick:)]subscribeNext:^(id x) {
        NSLog(@"点击红色按钮");
        }];
        2.下面block里的点怎么取了出来
        [RACObserve(_button, center) subscribeNext:^(id x) {

        NSLog(@"centerX:%@",x);
        }];
        _button.center = CGPointMake(60, 60);
      • 80c55abaa824:看一半,很用心啊,上完班再看,很赞
      • jobs_chen:敢问公司名字是什么?地址在哪?创业的项目是什么?做的是什么方面的业务?本人IT民工一枚,工作五年,想找一个有想法、且创意不错的团队,
        袁峥:@jobs_chen 广州小码哥
      • 丶纳凉:信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。

        RACSignal
      • 翀鹰精灵:峥老师这个是OC版的RAC用法,现在'ReactiveCocoa', '~> 5.0.1'了,有swift用法的干货讲解吗 ?期待呀(☆▽☆)
      • dushiling:我建好项目,pod进库,打开项目,就提示:Convert to Current Swift Syntax?

        The targets “ReactiveCocoa” and “Result” contain source code developed with an earlier version of Swift.
        Choose “Convert” to update the source code in this project to the latest SDKs. You will be given the choice to use Swift 2.3 syntax or update to Swift 3.
        This action can be performed later using “Convert to Current Swift Syntax” in the Edit menu.
        是swift 3.0 和之前的版本不兼容吗?怎么办
        dushiling:@袁峥Seemygo 我不太明白哎,pod “这里该怎么写” ?:relieved:
        袁峥:@逍遥_d22b 直接pod 中文版的
      • 何者越清:感觉这个挺不错的
      • 凤栖林:RACCommand这个例子为什么我只打印了执行命令,其他的block没执行,什么原因,代码按上面写的
      • RadioWaves:超级棒,支持下yuanzheng!
      • xiao_k龙:应该是RACSignal类吧 写错RACSiganl了
      • OnlyFunny:请问博主, 我现在用的是 reactiveCocoa 2.5 , 是不支持Swift的, 不是最新的5.0 , 请问代码上有什么区别吗?? 后期更新会不会有问题呢?? 导入支持swift的包以后 会大面积报错, 处理了好久都没有解决, 目前用的xcode8.2 请问有什么好办法解决 Swift包兼容问题??
      • 88cff6e7c3fe:感谢。
      • DSperson:您好 你的 最开始的 模仿Masonry 那个 demo 使用了 两个 类 和一个 Category 但是 那两个类(CaculatorMaker 和 CaculatorManager)其实是一个 给我 以前造成很大的烦恼 希望 您能改过来 或者 我的说明错误请您指出
      • 六月天空:用cocoaPods导入ReactiveCocoa提示use legacy swift language version...错误怎么我按照Edit > Convert > To Current Swift Syntax…转换swift语言版本, 还有错误 怎么解决啊
        jingijtk:你解决了吗,我也是这个问题
      • 会当凌绝顶丶: // 1.代替代理
        // 需求:自定义redView,监听红色view中按钮点击
        // 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
        // rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
        // 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
        [[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
        NSLog(@"点击红色按钮");
        }];
        这里不是很明白,求讲解
      • 会当凌绝顶丶:怎么手动添加ReactiveObjc阿.
      • 豆者:很详细,谢了,辛苦。。。。。。。。了,,正在学习这个。
      • 844feefb51b5:好东西,学习了 :+1: :+1: :+1:
      • 1e7177db6577:不通俗易懂,如果能加些理解性注释就好了。
      • 58cb860debdd:你好,用rac设置的通知,在dealloc时是否需要移除通知?还是rac会自动做这件事。
        y角:@XDKHAN 这个不需要移除吗,rac内部会主动实现
      • 鬼谷门生:袁老师 你们最近有没有在研究MVVM相关啊,好希望你能出一篇关于MVVM的文章
      • 34f3bd733daf:好厉害,虽然大部分都看的有点晕,但是把最常用的搞懂就可以
      • 流浪_先生:COOL MARK
      • 1c15d89da62c:明显就是李明杰视频里的。
      • XiaoYin:难道是葫芦娃的那个好友 阿峥?
      • 代码守望者:[subscriber sendCompleted];
        不添加这个代码有什么影响呢?我尝试了下去掉这个代码,一样可以执行
      • 71da97fe9e61:回去再看
      • 萧旭:请问,函数式编程,那两个函数如何实现的呢?
      • 你好Leon:有demo吗
      • 郑明明:很好的教程。。不过新手看起来好头疼
      • 轩辕小羽:您好,这里我有一个疑问,用rac代替代理,感觉不如第二个控制器加一个block属性,不是比这个更方便吗?我觉得用rac反而更费劲,用rac的好处究竟在哪里呢?
        冰三尺:@轩辕小羽 我和你有同样的看法,如果仅仅是从后往前传个值,觉的还不如使用block来的更方便。
      • jiodg45:博主,请教下2个问题,可能有点low。
        1.采用add(1).add(2).... 点语法在OC中不是只有属性可以使用么,这里即没有定义属性,也没有定义setter方法,这中间是怎么实现的呢? 在 return ^(int num) {
        self.result = self.result - num;
        return self;
        }; num直接获取到了 add()后中间的参数,这是一个什么样的实现逻辑呢?
        2.使用mgr对象 调用 add方法, 而定义的add方法 为-(Calculator*(^)(int))add. 单单分析这个方法的结构来看,仅仅只返回了一个Block。
        通过对下面三种Block进行测试,发现在我使用msg. 点语法的时候,Xcode给出提示
        M CaculatorMaker* (^)(int)add
        P CaculatorMaker* (^)(int)addBlock
        而对应的 addTest: :带有两参数的方法没有敲出来。
        @property (strong, nonatomic) CaculatorMaker*(^addBlock)(int resutlt);
        - (CaculatorMaker*(^)(int))addTest:(int)a :(int)b;
        - (CaculatorMaker*(^)(int))add;
        没能搞明白之间的原因,博主有可以帮忙脑补一下么?
        jiodg45:@jiodg45 再请教个问题, 如果定义的result为 strong类型,用self还安全么?会不回造成循环引用。
      • DSperson:@峥吖 请问你上面那个自己模仿那个有demo吗 我看的非常混乱,尝试了几次都没成功
      • Link913:峥哥,command的excute后面的参数随便传是吗?
      • 赵先生咯:哥,我就想知道,那个#import "metamacros.h"
        file not found 怎么解决的,能否告知一下
      • 51a9120806cc:要是上传个demo,就nice了
      • 14aa9fe20aef:感谢作者这么系统的整理
      • Link913:峥哥,是这样子的,推荐控制器是主页控制器的一个childcontroller,我在推荐控制器使用command获取到数据后短暂延迟后推荐控制器也能把传的数据打印出来,可是主页控制器也就是推荐的父控制器的方法不走啊,跨控制器传值或者穿信号到底该怎么做呢

        //在推荐控制器内获取数据,同时将数据用信号发送出来
        [recommendVC loadData];
        //在推荐控制器内部接收到信号同时能够打印出来
        [recommendVC logData];
        //在首页(当前控制器)获取信号,同时希望能够打印出信号所传的值,但是这个方法每次都跳过了
        [recommendVC.command.executionSignals subscribeNext:^(id x) {

        FYLog(@"1");

        FYLog(@"%@",x);

        [x subscribeNext:^(id x) {
        FYLog(@"shouye");
        }];

        }];
      • 3b73ac1e6515:不使用cocoapods导入,怎么导入RAC电脑安装不了cocoapods :sob:
        码渣:把reactivecocoa拖进工程,然后就是把<>换成"" 就差不多了
        ec4e3d0f5959:@cd_chen 同问
      • 我的大名叫小爱:那个 skip 到底表示的是什么含义啊 不是太明吧 0 1 2 好像结果都一样呢./?跪求大神们解答.
      • 我的大名叫小爱:又看了两遍 ...好文章...
      • 0ac55672768d:太6了
      • 我的大名叫小爱:原来你跟mj 大神是一间公司啊 ...这个框架有没有详细的教程啊 ...
      • Timi:表示还没用过 是不是太low了
      • 汤姆没有猫:大神有SWIFT版的demo吗
      • Capacity:不错的文章,谢谢楼主
      • 西江月:赶紧收藏了,学习受益
      • 小怡情ifelse:还没看完,简单一看 感觉很吊的样子 先评论顶一个
      • 531501205570:刚需!谢谢分享
      • LJ的ios开发:初学ReactiveCocoa的神器
      • 鬼晓晓:查资料找ReactiveCocoa 的时候找到的第一篇就是老师的博客
      • Cuffy:mark
      • 874485118050:官网的文档实在是。。。看这个全明白了
      • Mirinda7day:写得好
      • 然必果:RACSiganl 是不是写错了?
      • 22d5bd1b5691:去你吗的!上面代码到处都是错误!菜逼!

      本文标题:最快让你上手ReactiveCocoa之基础篇

      本文链接:https://www.haomeiwen.com/subject/cfkwcttx.html