美文网首页
ReactiveCocoa学习日记(一)

ReactiveCocoa学习日记(一)

作者: 黑黝黝的搬砖王 | 来源:发表于2018-01-09 15:58 被阅读0次

    ReactiveCocoa是Github开源的一个用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称。敢自称为 xxx Cocoa框架可以想象到这个框架的牛逼!!!膜拜......

    现在分为ReactiveObjC和ReactiveSwift,两个框架的功能使用相似,本文主要介绍ReactiveObjC的常用关键词和简单使用,希望能对你有所帮助......

    可以在ReactiveObjC github API中学习

    一、ReactiveObjC常用关键字

    • 1、 bind方法使用步骤:

    1.传入一个返回值RACStreamBindBlock的block。
    2.描述一个RACStreamBindBlock类型的bindBlock作为block的返回值。
    3.描述一个返回结果的信号,作为bindBlock的返回值。
    注意:在bindBlock中做信号结果的处理。
    这里需要手动导入#import <ReactiveCocoa/RACReturnSignal.h>,才能使用RACReturnSignal。

    [[_textField.rac_textSignal bind:^RACStreamBindBlock{
        // 什么时候调用:
        // block作用:表示绑定了一个信号.
        return ^RACStream *(id value, BOOL *stop){
            // 什么时候调用block:当信号有新的值发出,就会来到这个block。
            // block作用:做返回值的处理
            // 做好处理,通过信号返回出去.
            return [RACReturnSignal return:[NSString stringWithFormat:@"输出:%@",value]];
        };
    }] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    • 2、flattenMap,Map用于把源信号内容映射成新的内容。
    UITextField *textfield = [UITextField new];
        [[textfield.rac_textSignal flattenMap:^RACStream *(id value) {
            return [RACReturnSignal return:[NSString stringWithFormat:@"转换:%@",value]];
        }] subscribeNext:^(NSString * _Nullable x) {
        }];
    
     [[_textField.rac_textSignal map:^id(id value) {
            // 当源信号发出,就会调用这个block,修改源信号的内容
            // 返回值:就是处理完源信号的内容。
            return [NSString stringWithFormat:@"输出:%@",value];
        }] subscribeNext:^(id x) {
    
            NSLog(@"%@",x);
        }];
    

    FlatternMap和Map的区别
    1.FlatternMap中的Block返回信号。
    2.Map中的Block返回对象。
    3.开发中,如果信号发出的值不是信号,映射一般使用Map
    4.开发中,如果信号发出的值是信号,映射一般使用FlatternMap。
    总结:signalOfsignals用FlatternMap。

    • 3、concat:按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。
    • 4、then:用于连接两个信号,当第一个信号完成,才会连接then返回的信号。
    • 5、merge:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用。
    // merge:把多个信号合并成一个信号
        //创建多个信号
        RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@1];
            return nil;
        }];
        
        RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@2];
            return nil;
        }];
    
    // 合并信号,任何一个信号发送数据,都能监听到.
    RACSignal *mergeSignal = [signalA merge:signalB];
       [mergeSignal subscribeNext:^(id x) {
         NSLog(@"%@",x);
       }];
    
    // 底层实现:
    // 1.合并信号被订阅的时候,就会遍历所有信号,并且发出这些信号。
    // 2.每发出一个信号,这个信号就会被订阅
    // 3.也就是合并信号一被订阅,就会订阅里面所有的信号。
    // 4.只要有一个信号被发出就会被监听。
    
    • 6、zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件
    RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            [subscriber sendNext:@1];
            return nil;
        }];
        
        RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            [subscriber sendNext:@3];
            return nil;
        }];
        
        RACSignal *zipSignal = [signal1 zipWith:signal2];
        [zipSignal subscribeNext:^(id  _Nullable x) {
            NSLog(@"%@",x);
        }];
    

    底层实现:
    1.定义压缩信号,内部就会自动订阅signalA,signalB
    2.每当signalA或者signalB发出信号,就会判断signalA,signalB有没有发出个信号,有就会把最近发出的信号都包装成元组发出。

    • 7、combineLatest:将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。 基本上和zipWith没什么区别
    • 8、reduce聚合:用于信号发出的内容是元组,把信号发出元组的值聚合成一个值
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
         [subscriber sendNext:@1];
         return nil;
     }];
     
     RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
         [subscriber sendNext:@2];
         return nil;
     }];
    

    聚合
    常见的用法,(先组合在聚合)。
    combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock
    reduce中的block简介: reduceblcok中的参数,有多少信号组合,reduceblcok就有多少参数,每个参数就是之前信号发出的内容 reduceblcok的返回值:聚合信号之后的内容。

    RACSignal *reduceSignal = [RACSignal combineLatest:@[signalA,signalB] reduce:^id(NSNumber *num1 ,NSNumber *num2){
        return [NSString stringWithFormat:@"%@ %@",num1,num2]; 
    }];
     [reduceSignal subscribeNext:^(id x) {
         NSLog(@"%@",x);
     }];
    

    底层实现: 订阅聚合信号,每次有内容发出,就会执行reduceblcok,把信号内容转换成reduceblcok返回的值

    • 9、filter:过滤信号,使用它可以获取满足条件的信号.
    [textfield.rac_textSignal filter:^BOOL(NSString *value) {
            return value.length > 3;
        }];
    
    • 10、ignore:忽略完某些值的信号.
    [[textfield.rac_textSignal ignore:@"1"] subscribeNext:^(NSString * _Nullable x) {
            NSLog(@"%@",x);
        }];
    
    • 11、distinctUntilChanged:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
      过滤,当上一次和当前的值不一样,就会发出内容。
      在开发中,刷新UI经常使用,只有两次数据不一样才需要刷新
    [[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {    NSLog(@"%@",x);
    }];
    
    • 12、take:从开始一共取N次的信号 take后面即为从第几次开始取到第N次信号结束
    // 1、创建信号
    RACSubject *signal = [RACSubject subject];
    // 2、处理信号,订阅信号
    [[signal take:1] subscribeNext:^(id x) { 
        NSLog(@"%@",x);
    }];
    // 3.发送信号
    [signal sendNext:@1];
    [signal sendNext:@2];
    
    • 13、takeLast:取最后N次的信号,前提条件,订阅者必须调用完成,因为只有完成,就知道总共有多少信号.
    // 1、创建信号
    RACSubject *signal = [RACSubject subject];
    // 2、处理信号,订阅信号
    [[signal takeLast:1] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    // 3.发送信号
    [signal sendNext:@1];
    [signal sendNext:@2];
    [signal sendCompleted];
    
    • 14、takeUntil:(RACSignal *):获取信号直到某个信号执行完成
      监听文本框的改变直到当前对象被销毁
    [_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
    
    • 15、skip:(NSUInteger):跳过几个信号,不接受。
      表示输入第一次,不会被监听到,跳过第一次发出的信号
    [[_textField.rac_textSignal skip:1] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    • 16、switchToLatest:用于signalOfSignals(信号的信号),有时候信号也会发出信号,会在signalOfSignals中,获取signalOfSignals发送的最新信号。
    RACSubject *signalOfSignals = [RACSubject subject];
    RACSubject *signal = [RACSubject subject];
    

    获取信号中信号最近发出信号,订阅最近发出的信号。 // 注意switchToLatest:只能用于信号中的信号

    [signalOfSignals.switchToLatest subscribeNext:^(id x) {
       
        NSLog(@"%@",x);
    }];
    [signalOfSignals sendNext:signal];
    [signal sendNext:@1];
    
    • 17、doNext: 执行Next之前,会先执行这个Block
      doCompleted: 执行sendCompleted之前,会先执行这个Block
    [[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            [subscriber sendNext:@1];
            [subscriber sendCompleted];
            return nil;
        }] doNext:^(id  _Nullable x) {
             // 执行[subscriber sendNext:@1];之前会调用这个Block
             NSLog(@"doNext");
        }] doCompleted:^{
             // 执行[subscriber sendCompleted];之前会调用这个Block
             NSLog(@"doCompleted");
        }] subscribeNext:^(id  _Nullable x) {
            NSLog(@"%@",x);
        }];
    
    • 18、deliverOn: 内容传递切换到制定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用。
      subscribeOn: 内容传递和副作用都会切换到制定线程中。
    • 19、timeout:超时,可以让一个信号在一定的时间后,自动报错。
    [[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            [subscriber sendNext:@1];
            return nil;
        }] timeout:2 onScheduler:[RACScheduler currentScheduler]]subscribeNext:^(id  _Nullable x) {
                NSLog(@"%@",x);
        } error:^(NSError * _Nullable error) {
            // 1秒后会自动调用
            NSLog(@"%@",error);
        }];
    
    • 20、interval 定时:每隔一段时间发出信号
    [[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id  _Nullable x) {
            NSLog(@"%@",x);
        }];
    
    • 21、delay 延迟发送next。
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
       
        [subscriber sendNext:@1];
        return nil;
    }] delay:2] subscribeNext:^(id x) {
      
        NSLog(@"%@",x);
    }];
    
    • 22、 retry重试 :只要失败,就会重新执行创建信号中的block,直到成功.
    __block int i = 0;
        [[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            if (i == 10) {
                [subscriber sendNext:@1];
            }else{
                NSLog(@"接受错误%d",i);
                [subscriber sendError:nil];
            }
            i++;
    
            return nil;
        }] retry] subscribeNext:^(id  _Nullable x) {
            NSLog(@"%@",x);
        } error:^(NSError * _Nullable error) {
            NSLog(@"%@",error);
        }];
    
    • 23、 replay重放:当一个信号被多次订阅,反复播放内容
    RACSignal *signal = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            [subscriber sendNext:@1];
            [subscriber sendNext:@2];
            return nil;
        }] replay];
        
        [signal subscribeNext:^(id  _Nullable x) {
            NSLog(@"第一个订阅者%@",x);
        }];
        
        [signal subscribeNext:^(id  _Nullable x) {
            NSLog(@"第二个订阅者%@",x);
        }];
    
    • 24、throttle节流:当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。
    RACSubject *signal = [RACSubject subject];
    // 节流,在一定时间(1秒)内,不接收任何信号内容,过了这个时间(1秒)获取最后发送的信号内容发出。
    [[signal throttle:1] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    

    二、ReactiveObjC常用方法

    • 1、rac遍历 NSArray、NSDictionary
    NSArray *array = @[@"1",@"2",@"3",@"4",@"5"];
        [array.rac_sequence.signal subscribeNext:^(id  _Nullable x) {
            NSLog(@"%@",x);
        }];
    
    NSDictionary *dictionary = @{@"key1":@"value1", @"key2":@"value2", @"key3":@"value3"};
        [dictionary.rac_sequence.signal subscribeNext:^(RACTuple*  _Nullable x) {
            RACTupleUnpack(NSString *key, NSString *value) = x;//返回的是元祖类型,RACTupleUnpack解出数据
            NSLog(@"键%@-->值%@",key,value);
        }];
    
    • 2、rac 帅选数组
    NSArray *array = @[@"1",@"2",@"3",@"4",@"5"];
        NSArray *newArray = [[array.rac_sequence map:^NSString*(NSString *value) {
            return [value integerValue] > 3 ? value : nil;
        }] array];
    
    • 3、rac监听textField
    [[textField.rac_textSignal filter:^BOOL(NSString *value) {
            return value.length > 5;
        }] subscribeNext:^(NSString * _Nullable x) {
            NSLog(@"%@",x);
        }];
    
    • 4、rac监听按钮点击
    [[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
       
        NSLog(@"%@ 按钮被点击了", x); // x 是 button 按钮对象
    }];
    
    • 5、登录按钮状态实时监听

    下面表示只有 用户名 和 密码 输入框内容都大于 0 时,登录 按钮才可以点击,而且状态是实时监听的,一句代码就能完成这个功能。

       RAC(self.logingBtn,enabled) = [RACSignal combineLatest:@[self.nameTextField.rac_textSignal,self.passwordTextField.rac_textSignal] reduce:^id _Nonnull(NSString *name,NSString *pwd){
            return @(name.length > 0 && pwd.length > 0);
        }];
    
    • 6、监听 Notification 通知事件

    可省去在 -(void)dealloc {} 中清除通知和监听通知创建方法的步骤。

    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
            NSLog(@"%@ 键盘弹起", x); // x 是通知对象
        }];
    
    • 7、 代替 Delegate 代理方法

    可以代替 KVO 监听,下面表示把监听 view 的 frame 属性改变转换成信号,只要值改变就会发送信号。

    [[view rac_valuesForKeyPath:@"frame" observer:self] subscribeNext:^(id  _Nullable x) {
        
        NSLog(@"属性的改变:%@", x); // x 是监听属性的改变结果
    }];
    

    还有一种更简单的写法,就是利用 RAC 的宏,和上面的效果是一样的。

    [RACObserve(view, frame) subscribeNext:^(id  _Nullable x) {
        
        NSLog(@"属性的改变:%@", x); // x 是监听属性的改变结果
    }];
    
    • 9、 代替 NSTimer 计时器

    可以代替 NSTimer 使用。

    @interface ViewController ()
    
    @property (nonatomic, strong) RACDisposable *disposable;
    
    @end
    
    /* 定义计时器监听 */
    self.disposable = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
        
        NSLog(@"当前时间:%@", x); // x 是当前的系统时间
        
        /* 关闭计时器 */
        [_disposable dispose];
    }];
    

    三、ReactiveObjC登录注册模拟实例

    • vc中代码
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self bindModel];
        // Do any additional setup after loading the view from its nib.
    }
    
    // 视图模型绑定 
    - (void)bindModel
    {
        RAC(self.logingBtn,enabled) = [RACSignal combineLatest:@[self.nameTextField.rac_textSignal,self.passwordTextField.rac_textSignal] reduce:^id _Nonnull(NSString *name,NSString *pwd){
            return @(name.length > 0 && pwd.length > 0);
        }];
        
    //    RAC(self.loginViewModel.account, account) = _nameTextField.rac_textSignal;
    //    RAC(self.loginViewModel.account, pwd) = _passwordTextField.rac_textSignal;
        
        //绑定登录按钮
    //    RAC(self.logingBtn,enabled) = self.loginViewModel.enableLoginSignal;
        
        //监听登录按钮的点击
        [[self.logingBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
            
            [self.loginViewModel.loginCommand execute:nil];
            
        }];
    }
    
    -(LogingViewModel *)loginViewModel
    {
        if (!_loginViewModel) {
            _loginViewModel = [[LogingViewModel alloc] init];
        }
        
        return _loginViewModel;
    }
    
    • viewmodel中代码
    -(instancetype)init
    {
        if (self = [super init]) {
            [self initialBind];
        }
        return self;
    }
    
    -(Account *)account
    {
        if (!_account) {
            _account = [[Account alloc] init];
        }
        return _account;
    }
    
    // 初始化绑定
    - (void)initialBind
    {
        // 监听账号、密码的属性值改变,把他们聚合成一个信号。
    //    _enableLoginSignal = [RACSignal combineLatest:@[RACObserve(self.account, account),RACObserve(self.account, pwd)] reduce:^id (NSString *account,NSString *pwd){
    //        return @(account.length && pwd.length);
    //    }];
        
        _loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * (id input) {
            NSLog(@"点击了登录");
            
            return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
               
                //模拟网络延迟
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                   
                    [subscriber sendNext:@"登陆成功"];
                    // 数据传送完毕,必须调用完成,否则命令永远处于执行状态
                    [subscriber sendCompleted];
                });
                
                return nil;
            }];
        }];
        
        //监听登录产生的数据
        [_loginCommand.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
            if ([x isEqualToString:@"登陆成功"]) {
                NSLog(@"登录成功");
            }
        }];
        
        //监听登录状态
        [[_loginCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
           
            if ([x isEqual:@(YES)]) {
                // 正在登录ing...
                MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:[UIApplication sharedApplication].keyWindow animated:YES];
                hud.mode = MBProgressHUDModeDeterminate;
                hud.label.text = @"正在登录";
            }else{
                //登陆成功
                [MBProgressHUD hideHUDForView:[UIApplication sharedApplication].keyWindow animated:YES];
            }
        }];
    }
    

    上面的Demo

    未完待续,后续会在项目中实际使用RAC+MVVM方式来实现函数响应式编程

    相关文章

      网友评论

          本文标题:ReactiveCocoa学习日记(一)

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