ReactiveCocoa 初探

作者: 天空中的球 | 来源:发表于2015-12-21 18:32 被阅读1630次

    最近每周末都会看叶孤城的直播,对于我们iOS开发者来说,确实是一个福利,很感谢他们的分享精神,收获到的一些东西特此记录下。

    12月19号 ReactiveCocoa

    昨天听了DeveloperLx的视频之后,对ReactiveCocoa有了个初步的认识下,暂时可能不会用到,但是了解还是必须的。ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,兼具函数式编程响应式编程 的特性,可以很好的用于消息传递、回调机制复杂等问题,使之清晰化,条理化。

    ReactiveCocoa结合了一些编程模式:

    • 函数式编程:利用高阶函数,即将函数作为其它函数的参数。
    • 响应式编程:关注于数据流及变化的传播。

    基于以上两点,ReactiveCocoa被当成是函数响应编程(Functional Reactive Programming, FRP)框架。

    一、导入ReactiveCocoa 框架

    我们可以直接进入到ReactiveCocoa的github了解下,通常我们用CocoaPods就OK啦

    pod 'ReactiveCocoa'
    

    很多情况下,直接导入就可以了,但是这里会报这个错

    屏幕快照 2015-12-20 下午4.58.42.png

    需要在Podfile加上use_frameworks!,重新pod install 才能导入成功

    use_frameworks!
    pod 'ReactiveCocoa'
    

    但是我使用Xcode7.2的时候,还是出现下面这个问题

    Box.swift: error: 'Printable' has been renamed to 'CustomStringConvertible'
    Box.swift: error: 'toString' has been renamed to 'String'
    Box/MutableBox.swift: error: 'Printable' has been renamed to 'CustomStringConvertible'
    MutableBox.swift: error: 'toString' has been renamed to 'String'
    

    大致原因是 这个默认的分支中 swift 不支持swift2.0版的,然后我就视图转换成~> 4.0.4-alpha-1就OK了

    use_frameworks!
    pod 'ReactiveCocoa','~> 4.0.4-alpha-1'
    

    If you would prefer to use CocoaPods, there are some unofficial pod specs) that have been generously contributed by third parties

    二、基本使用

    #import <ReactiveCocoa/ReactiveCocoa.h> // 导入头文件
    
    2-1、监听文本框使用
    - (void)learnRACWithTextFiled
    {
    //    // 直接监听 textFiled的改变
    //    [[self.testTextField rac_signalForControlEvents:UIControlEventEditingChanged] subscribeNext:^(id x){
    //        
    //        NSLog(@"%@", x);
    //    
    //    
    //    }];
        
        // 或者
        [self.testTextField.rac_textSignal subscribeNext:^(NSString * textString) {
            
            NSLog(@"%@", textString);
        }];
        
    }
    // 打印出其textFiled中的文本信息来
    
    2-3、 监听Button事件
    - (void)learnRACWithButton
    {
        [[self.testButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
            
            NSLog(@"按钮被点击了");
        }];
        
    }
    
    2-3、手势
    - (void)learnRACWithGesture
    {
        UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc]init];
        [self.view addGestureRecognizer:tap];
        
        [[tap rac_gestureSignal] subscribeNext:^(UITapGestureRecognizer * tap) {
            
            // 点击可以
             [[[UIApplication sharedApplication] keyWindow] endEditing:YES];
        }];
    
    }
    
    2-4、通知
    -  (void)learnRACWithNSNotificationCenter
    {
        // 通知可以不移除
        [[[NSNotificationCenter defaultCenter]
          rac_addObserverForName:UIKeyboardWillShowNotification object:nil]
                   subscribeNext:^(NSNotification * notification) {
            
            NSLog(@"show");
        
        }];
    }
    
    2-5、定时器
    -  (void)learnRACWithNSTimer
    {
        NSLog(@"begin");
        //    1. 延迟某个时间后再做某件事
        [[RACScheduler mainThreadScheduler]afterDelay:2.0f schedule:^{
            
            NSLog(@"2秒之后发生的事情");
           
        }];
        
    //    2. 每个一定长度时间做一件事
        [[RACSignal interval:4 onScheduler:[RACScheduler mainThreadScheduler]]subscribeNext:^(NSDate * date) {
            
            NSLog(@"每隔几秒发生的事情");
        }];
        /*
         2015-12-21 13:22:23.209 ReactiveCocoaLearn[78775:4675706] begin
         2015-12-21 13:22:25.409 ReactiveCocoaLearn[78775:4675706] 2秒之后发生的事情
         2015-12-21 13:22:27.213 ReactiveCocoaLearn[78775:4675706] 每隔几秒发生的事情
         2015-12-21 13:22:31.211 ReactiveCocoaLearn[78775:4675706] 每隔几秒发生的事情
         
         */
    
    }
    
    2-6、代理

    但是有局限,只能取代没有返回值的代理方法

    - (void)learnRACWithProtocol
    {
        UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"RAC中Protocol"
                                                            message:@"UIAlertView"
                                                           delegate:self
                                                  cancelButtonTitle:@"Cancel"
                                                  otherButtonTitles:@"OK", nil];
        [alertView show];
        [[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple * tuple) {
            
            //可以多尝试下RACTuple里的属性
            NSLog(@"tuple.second == %@",tuple.second);
            if([tuple.second isEqualToNumber:@0])
            {
                NSLog(@"cancel");
            }
            if([tuple.second isEqualToNumber:@1])
            {
                NSLog(@"ok");
            }
            
        }];
        
        //  更简单的方式:
    //        [[alertView rac_buttonClickedSignal]subscribeNext:^(id x) {
    //            //可以多尝试下RACTuple里的属性
    //            NSLog(@"%@",x);
    //            if([x isEqualToNumber:@0])
    //            {
    //                NSLog(@"Cancel");
    //            }
    //            if([x isEqualToNumber:@1])
    //            {
    //                NSLog(@"Ok");
    //            }
    //            
    //        }];
        
    }
    
    2-7、KVO
    [RACObserve(self.testScrollerView, contentOffset) subscribeNext:^(id x) {
        
         NSLog(@"Offset=%@",x);
    }];
    
    /*
     2015-12-21 15:06:23.689 ReactiveCocoaLearn[81607:4756461] Offset=NSPoint: {0, 0}
     2015-12-21 15:06:23.689 ReactiveCocoaLearn[81607:4756461] Offset=NSPoint: {0, 0}
     2015-12-21 15:06:23.711 ReactiveCocoaLearn[81607:4756461] Offset=NSPoint: {0, -1}
     2015-12-21 15:06:23.790 ReactiveCocoaLearn[81607:4756461] Offset=NSPoint: {0, -1.5}
     2015-12-21 15:06:23.870 ReactiveCocoaLearn[81607:4756461] Offset=NSPoint: {0, -2}
     
     */
    

    或是

       [[self.greenView rac_valuesAndChangesForKeyPath:@"center"
                                            options:NSKeyValueObservingOptionNew observer:nil]
     subscribeNext:^(id x) {
        
        NSLog(@"center===%@",x);
        
    }];
    
    /*
     center===<RACTuple: 0x7fc7205138c0> (
     "NSPoint: {187.5, 333.5}",
     {
     kind = 1;
     new = "NSPoint: {187.5, 333.5}";
     }
     )
     */
    

    以上是一些RAC的基本用法,熟练这几个以后,我们很多场景都能运用自如,而且会发现RAC真的很方便。

    三、RACSignal使用

    其实在RAC中最核心的类RACSiganl,搞定这个类就能用ReactiveCocoa开发了。

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

    创建信号 & 激活信号 & 废弃信号

    // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
    // 2.订阅信号,才会激活信号. - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
    // 3.发送信号 - (void)sendNext:(id)value
    // 4.废弃信号  RACDisposable  
    
      // 创建信号
     RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        // block调用时刻:每当有订阅者订阅信号,就会调用block。
        
        // 发送信号
        [subscriber sendNext:@1];
        
        // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
        [subscriber sendCompleted];
        
        return [RACDisposable disposableWithBlock:^{
            // 销毁信号
            // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
            // 执行完Block后,当前信号就不在被订阅了。
            
            NSLog(@"信号销毁");
            
          }];
      }];
    
    // 订阅信号,才会激活信号.
       [siganl subscribeNext:^(id x) {
            NSLog(@"接到数据x=%@",x);
        }];
    /*
     2015-12-21 15:47:18.335 ReactiveCocoaLearn[82287:4789675] 接到数据x=1
     2015-12-21 15:47:18.335 ReactiveCocoaLearn[82287:4789675] 信号销毁
     */
    
    信号的处理
    3-1、map
    [[self.testTextField.rac_textSignal map:^id(NSString *textStr){
    
        return @(textStr.length);
    }] subscribeNext:^(id x){
        
        NSLog(@"x==%@",x);
    }];
    // 映射
    
    3-2、filter
    [[[self.testTextField.rac_textSignal map:^id(NSString *textStr){
    
        return @(textStr.length);
    }] filter:^BOOL(NSNumber * value){
        
        return value.integerValue > 2;
        
    }] subscribeNext:^(id x){
        
        NSLog(@"x==%@",x);
    }];
    

    过滤掉一部分

    3-3、delay
    RACSignal *siganl = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        NSLog(@"realySendSignal");
        [subscriber sendNext:@1];
        [subscriber sendCompleted];
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"discard Signal");
        }];
    }] delay:3];
    NSLog(@"SubscriSiganl");
    [siganl subscribeNext:^(id x) {
        
        NSLog(@"recevieSiganl=%@",x);
    }];
    
    // 延迟3秒才接收数据
    /*
     2015-12-21 16:33:05.326 ReactiveCocoaLearn[83488:4831881] 开始预订信号
     2015-12-21 16:33:05.327 ReactiveCocoaLearn[83488:4831881] 真正发送信号
     2015-12-21 16:33:05.328 ReactiveCocoaLearn[83488:4831881] 销毁信号
     2015-12-21 16:33:08.621 ReactiveCocoaLearn[83488:4831881] 接收信号=1
     */
    

    注意打印的时间,发送信号,订阅信号 的时间,再次了解下整个流程。

    3-4、startWith
    RACSignal *siganl = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
    
        [subscriber sendNext:@"one"];
        [subscriber sendCompleted];
        
        return [RACDisposable disposableWithBlock:^{
           
        }];
    }] startWith:@"two"];
    
    [siganl subscribeNext:^(id x) {
        
        NSLog(@"接收信号=%@",x);
    }];
    
    // 2015-12-21 16:38:27.160 ReactiveCocoaLearn[83642:4836850] 接收信号=two
    // 2015-12-21 16:38:27.162 ReactiveCocoaLearn[83642:4836850] 接收信号=one
    

    相当于在发送某个信号之前先发送另一个信号

    3-5、timeout
    RACSignal *siganl = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        // 假设某个请求的时间用了几秒
        [[RACScheduler  mainThreadScheduler] afterDelay:4 schedule:^{
        
            [subscriber sendNext:@"one"];
            [subscriber sendCompleted];
        }];
    
        return [RACDisposable disposableWithBlock:^{
    //            NSLog(@"销毁信号");
        }];
        // 然后timeout就是当超过这个时间的时候就会出错
    }] timeout:10.0 onScheduler:[RACScheduler mainThreadScheduler]];
    
    
    [siganl subscribeNext:^(id x){
    
        
        NSLog(@"x==%@",x);
    
    } error:^(NSError * error){
    
        // 这个地方就很容易来处理错误的时候啦
        NSLog(@"error==%@",[error description]);
    
    } completed:^{
    
        NSLog(@"completed");
    }];
    

    比较适合用于 请求超时的时候

    3-6、take & skip & takeLast
     RACSignal *siganl = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    
        [subscriber sendNext:@"one"];
        [subscriber sendNext:@"two"];
        [subscriber sendNext:@"three"];
        [subscriber sendNext:@"four"];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
    
        }];
        
    }] take:2];
    
    [siganl subscribeNext:^(id x){
    
        NSLog(@"x==%@",x);
    
    }];
    
    //take 只接收前几次
    //skip 跳过前几次
    //takeLast 只接收最后几次
     / *
        takeUntilBlock:     
        takeWhileBlock:
        skipWhileBlock:
        skipUntilBlock:
      */
    

    四、进阶使用

    在我们向服务器进行请求的时候,RAC为我们带来了诸多方便的事情,值得探索。

    此处还是用DeveloperLx的例子,textFiled举例说明。

    4-1、throttle
    [[self.testTextField.rac_textSignal throttle:0.5]subscribeNext:^(id x){
        NSLog(@"%@", x); 
    }];
    

    就是在我们设置那个时间内(0.5秒),不会发送消息,让其不会一直不断的发送过来。

    4-2 distinctUntilChanged
       [[[self.testTextField.rac_textSignal throttle:0.5] distinctUntilChanged]subscribeNext:^(id x){
    
        NSLog(@"%@", x);
        
    }];
    

    相同的就不发送,直到有所该变再发送

    4-3 ignore
      [[[[self.testTextField.rac_textSignal throttle:0.5] distinctUntilChanged] ignore:@""] subscribeNext:^(id x){
    
        NSLog(@"%@", x);
        
    }];
    

    忽略某个值,像上面就是忽略 空值

    4-4 switchToLatest

    先综合了下 map

     [[[[[[self.testTextField.rac_textSignal throttle:0.5] distinctUntilChanged] ignore:@""] map:^id(id value){
    
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber>subscriber){
    
            [subscriber sendNext:value];
            [subscriber sendCompleted];
        
            return [RACDisposable disposableWithBlock:^{}];
        }];
    
    }]switchToLatest ]subscribeNext:^(NSString * x){
    
        NSLog(@"x==%@", x);
        
    }];
    

    只执行最后一次,这个地方有待推敲,暂时还不是很理解

    4-5 merge
    RACSignal * signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
         
            [subscriber sendNext:@"Signal_A"];
            [subscriber sendCompleted];
        });
        
        return nil;
    }];
    
    RACSignal * signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
         
       
            [subscriber sendNext:@"Signal_B"];
            [subscriber sendCompleted];
        });
        
        return nil;
    }];
    
    NSLog(@"开始预订");
    [[RACSignal merge:@[signalA, signalB]]subscribeNext:^(id x) {
        
        NSLog(@"x==%@",x);
    }];
    
     /*
      2015-12-21 17:54:24.105 ReactiveCocoaLearn[85576:4905054] 开始预订
      2015-12-21 17:54:26.306 ReactiveCocoaLearn[85576:4905054] x==Signal_A
      2015-12-21 17:54:27.398 ReactiveCocoaLearn[85576:4905054] x==Signal_B
      */
    

    同时订阅信号

    4-6 concat
    NSLog(@"开始预订");
    [[RACSignal concat:@[signalA, signalB]]subscribeNext:^(id x) {
        
        NSLog(@"x==%@",x);
    }];
    
    /*
      2015-12-21 17:57:03.718 ReactiveCocoaLearn[85651:4908056] 开始预订
      2015-12-21 17:57:05.720 ReactiveCocoaLearn[85651:4908056] x==Signal_A
      2015-12-21 17:57:09.012 ReactiveCocoaLearn[85651:4908056] x==Signal_B
    */
    

    执行完A 后才执行 B ,而且A必须成功,B才会执行,他们是异步请求.

    4-7、zipwith
    NSLog(@"开始预订");
    [[signalA zipWith:signalB] subscribeNext:^(id x) {
        
        NSLog(@"x==%@",x);
    }];
      /*
      2015-12-21 18:01:18.770 ReactiveCocoaLearn[85742:4913279] 开始预订
      2015-12-21 18:01:22.071 ReactiveCocoaLearn[85742:4913279] x==<RACTuple: 0x7f8cc8c2c520> (
          "Signal_A",
          "Signal_B"
    )
      */
    

    注意看上面返回的时间差距
    返回一个RACTuple(元祖) ,A、B 至少都发送过一次消息后,才返回。
    三者以上的可以用下面这个,combineLatest,同上

    [[RACSignal combineLatest:@[signalA,signalB,signalC]] subscribeNext:^(id x){
    
        NSLog(@"x==%@",x);
    }];
    

    五、RAC常见宏

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

     RAC(self.testButton, backgroundColor) = [RACObserve(self.testButton, selected) map:^UIColor *(NSNumber * selected) {
        
        return [selected boolValue] ? [UIColor redColor] : [UIColor greenColor];
    }];
    
    [[self.testButton rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(UIButton * btn) {
        
        btn.selected = !btn.selected;
    }];
    

    直接改变button 的颜色

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

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

    点击按钮,改变其center之后

    /*
      2015-12-21 18:18:52.229 ReactiveCocoaLearn[86031:4931305] NSPoint: {0, 0}
      2015-12-21 18:18:54.024 ReactiveCocoaLearn[86031:4931305] 按钮被点击了
      2015-12-21 18:18:54.025 ReactiveCocoaLearn[86031:4931305] NSPoint: {187.5, 333.5}
    */
    
    下面这个也是同样的用这个宏的,这是用最少的代码写一个秒表。
    RAC(self.testLabel, text) = [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] map:^NSString *(NSDate * date) {
        
        return date.description;
    }];
    

    总的来说,记录的笔记大致差不多了,有很多东西自己还没深入了解,毕竟我还没运用在项目中,初次记录,慢慢学习吧。再次还是非常感谢DeveloperLx,让我了解RAC的这么好用的东东,后期继续探索中,暂时记录到此。

    备注:

    DeveloperLx 的github微博,在此。
    另外参考了下列文章:
    http://www.jianshu.com/p/87ef6720a096
    http://southpeak.github.io/blog/2014/08/02/reactivecocoazhi-nan-%5B%3F%5D-:xin-hao/

    相关文章

      网友评论

      • 我想走走:话说叶孤城是谁?
        天空中的球:@峰峰爱码 一名 iOS 开发者,上面有其微博的。另外这是我之前的笔记,没有demo 的。
      • 我想走走:大神,给个Demo好不好,自己第一次看这个框架,感觉好难理解。15936527501@163.com。赞,赞,赞。 谢谢
      • 729dc93b172c:文章开头说每周看叶孤城的直播,在哪看啊
        729dc93b172c:哦好吧,我最近项目在使用RAC刚好看到你的文章 想问你个问题,上代码你帮我看看可以吗

        //整合信号判断是否都输入了,按钮可不不可点击
        @weakify(self)

        RACSignal *phoneNumerTFrac=[self.phoneNumerTF.rac_textSignal filter:^BOOL(NSString* value) {
        @strongify(self)

        return [self isTextfieldRight:value];
        }];
        RACSignal *verifyCodeTFrac=[self.verifyCodeTF.rac_textSignal filter:^BOOL(NSString* value) {
        @strongify(self)

        return [self isTextfieldRight:value];
        }];
        RACSignal *shopNameTFFrac=[self.shopNameTF.rac_textSignal filter:^BOOL(NSString* value) {
        @strongify(self)

        return [self isTextfieldRight:value];
        }];
        RACSignal *passwordTFrac=[self.passwordTF.rac_textSignal filter:^BOOL(NSString* value) {
        @strongify(self)

        return [self isTextfieldRight:value];
        }];


        [[RACSignal combineLatest:@[phoneNumerTFrac, verifyCodeTFrac,shopNameTFFrac,passwordTFrac] reduce:^id(NSNumber*phoneNumerTF,NSNumber*verifyCodeTF,NSNumber*shopNameTF, NSNumber *passwordTF){

        DLog(@"%d==%d==%d==%d",[phoneNumerTF boolValue],[verifyCodeTF boolValue],[shopNameTF boolValue],[passwordTF boolValue]);

        return @([phoneNumerTF boolValue]&&[verifyCodeTF boolValue]&&[passwordTF boolValue]&&[shopNameTF boolValue]);
        }] subscribeNext:^(id x) {
        @strongify(self)
        self.registerBtn.enabled = [x boolValue];
        }];
        天空中的球:斗鱼的,但是现在木有直播啦
      • ChiOS:3-6、take & ship & takeLast 中我怎么感觉ship应该为skip :blush:
        天空中的球:@ChiOS 😄,谢谢,笔误啦

      本文标题:ReactiveCocoa 初探

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