XZ_iOS之RAC(ReactiveCocoa)的介绍和使用

RAC1-介绍
简介
GitHub团队开发的一套开源框架;超重量级的框架;接管了苹果中的所有消息机制;
全称 ReactiveCocoa, 响应式编程(FRP)在iOS 中的一个实现框架, 现在OC版本的只到3.0.0版本就不再更新了,swift版本有持续更新;

目的:事件监听
用信号接管了苹果中的所有事件机制,包括:
  1. addTarget:监听,点击按钮之后调用回调方法;
  2. 代理(delegate):在主控件里发生某个事情的时候,通过协议的方式通知我们去执行协议的方法;双方有个约定好的协议,调用方实现协议方法,接收方在需要的时候通知调用方delegate,执行协议方法。
  3. 通知:通过注册字符串的方式,当接收到通知之后,监听者去做一些事情;
  4. KVO:通过监听属性变化来实现监听
  5. 时钟
  6. 网络异步回调

block不是事件机制,block是提前好的代码传递给接受方,接收方在拿到block之后,在需要的时候直接执行block,不再跟回来有关系了。从调用方来说,准备好的block,传给接收方,我们是不管接收方什么时候执行的。所以,block不属于事件监听。

特点
学习起来非常难;团队开发时需特别谨慎,需要不断地代码评审,保证团队中所有人的代码风格一致!

RAC的重要概念
信号

RAC的核心思想
所谓响应,就是事件发生后做出响应

框架特点
  1. 超重量级的核心框架,学习成本较高
  2. 利用信号,接管iOS的所有事件
  3. 利用block将所有相关代码集中在一起,从一定程度上解决了代码分散的问题;
  4. 使用时需要注意循环引用,注册 rac_willDeallocSignal 信号能够跟踪对象是否被释放
  5. 通过KVO监听,能够及时将模型数据变化体现在界面上
RAC2-事件机制
RAC最为核心的概念:1>RAC接管了了所有的事件机制,2>信号刚刚创建的时候,是冷信号,不会工作,只有被订阅者订阅了之后才是热信号,才会执行。3>如何传递信息:在信号内部给订阅者通过send三个方法告诉订阅者他对应的方法,订阅者只需要监听不同的代码块,就可以在不同的代码块获得自己想要的东西了。

网络请求的时候,使用RAC实现监听,订阅者收到相应的方法时,获取自己想要的东西,并作出相应的操作。


XZPersonListModel.h
// 列表数据模型,负责加载数据 ( 包含网络数据 / 本地缓存数据 )
@interface XZPersonListModel : NSObject

// 联系人数组 , 泛型数组
@property ( nonatomic ) NSMutableArray < XZPerson *> *personList;

// 加载联系人数组 返回一个 RAC 的信号
- ( RACSignal *)loadPersons;

@end

XZPersonListModel.m

- ( RACSignal *)loadPersons {
   
    NSLog ( @"==============%s" , __FUNCTION__ );
   
    // 直接返回一个 RAC 的信号
    // 一旦有了订阅者, block 内部的代码能够执行
    return [ RACSignal createSignal :^ RACDisposable * _Nullable ( id < RACSubscriber _Nonnull subscriber) {

        // 发送不同的信号
        _personList = [ NSMutableArray array ];
       
        // 模拟异步加载数据
        dispatch_async ( dispatch_get_global_queue ( 0 , 0 ), ^{
           
            // 模拟延时
            [ NSThread sleepForTimeInterval : 1.0 ];
           
            // 创建数据
            for ( NSInteger i = 0 ; i < 20 ; i++) {
                XZPerson *person = [[ XZPerson alloc ] init ];
               
                person. name = [ @"zhangsan - " stringByAppendingFormat : @"%ld" ,( long )i];
                person. age = 15 + arc4random_uniform ( 20 );
               
                [ _personList addObject :person];
            }
           
            NSLog ( @"%@" , _personList );
           
            // 完成回调发送信号给订阅者,主线程
            dispatch_async ( dispatch_get_main_queue (), ^{
                BOOL isError = NO ;
                if (isError) {
                    [subscriber sendError :[ NSError errorWithDomain : @"cn.xzproject.error" code : 1001 userInfo : @{ @"error message" : @" 异常错误 " } ]];
                } else {
                    [subscriber sendNext : _personList ];
                }
               
                // 发送完成事件
                [subscriber sendCompleted ];
               
            });
        });
        return nil ;
    }];

}

ViewController.m

- ( void )loadData {
    // 1. 实例化视图模型
    _personListModel = [[ XZPersonListModel alloc ] init ];
    // 2. 加载数据
    /**
     next 是接收到数据
     error 接收到错误,错误处理
     completed 信号完成
     */
    [[ _personListModel loadPersons ] subscribeNext :^( id   _Nullable x) {
        NSLog ( @"==============%@" ,x);
        // 刷新数据
        [ self . tableNotice reloadData ];
    } error :^( NSError * _Nullable error) {
        NSLog ( @"==============%@" ,error);
    } completed :^{
        NSLog ( @"============== 完成 " );
    }];
}

RAC-监听按钮和输入框事件
可以查看框架中的各种方法,RAC封装了常用的UI控件方法,需要监听哪个控件,查看一下这个控件或者他的父类的方法即可。
因为UIButton是继承自UIControl的,UIButton分类中没有合适的方法,所以,可以使用UIControl分类的方法

监听按钮的事件 - 不再需要新建一个方法,在 block 里面实现相应事件, 用block把重构的相关代码放在一起
    [[btn rac_signalForControlEvents : UIControlEventTouchUpInside ] subscribeNext :^( __kindof UIControl * _Nullable x) {
        NSLog ( @"%@---------%@" ,x,[x class ]);
    }];

// [btn rac_signalForControlEvents:UIControlEventTouchUpInside] 是创建了一个冷信号,调用 subscribeNext 才订阅了信号,才会工作

监听文本输入框内容 - 参数就是输入的文本内容!
    [[nameTextField rac_textSignal ] subscribeNext :^( NSString * _Nullable x) {
        NSLog ( @"%@ %@" ,x,[x class ]);
    }];

组合信号
创建两个UITextField,将这两个的输入框的信号进行组合
UITextField *nameTextField = [[ UITextField alloc ] initWithFrame : CGRectMake ( 20 , 40 , 300 , 40 )];
    nameTextField. borderStyle = UITextBorderStyleRoundedRect ;
    [ self . view addSubview :nameTextField];
   
    UITextField *pwdTextField = [[ UITextField alloc ] initWithFrame : CGRectMake ( 20 , 100 , 300 , 40 )];
    pwdTextField. borderStyle = UITextBorderStyleRoundedRect ;
    [ self . view addSubview :pwdTextField];
   
    // 组合信号 Tuple 是元组 ,
    [[ RACSignal combineLatest : @[ nameTextField. rac_textSignal ,pwdTextField. rac_textSignal ] ] subscribeNext :^( RACTuple * _Nullable x) {
        NSString *name = x. first ;
        NSString *pwd = x. second ;
        NSLog ( @"name %@ pwd %@ [x class]:%@" ,name,pwd,[x class ]);
        // 打印结果 ===name Wertyui pwd 3456yui [x class]:RACTuple
    }];

当有多个控件的话,使用combineLatest:可能会很麻烦,我们可以使用c ombineLatest:reduce: 监听组合输入框事件
// reduce -> 减少的意思,合并两个信号的数据,进行汇总计算时使用的!
    // id 是返回值 ,参数是有括号的;
    // reduce 中,可以通过接收的参数进行计算,并且返回需要的数值!例:登录界面,只有用户名和密码同时存在,才允许登录!
   
    // 方法一:使用 __weak 避免循环引用
//    __weak typeof(self)weakSelf = self;
    // 方法二:
    @ weakify ( self );
    [[ RACSignal combineLatest : @[ nameTextField. rac_textSignal ,pwdTextField. rac_textSignal ] reduce :^ id ( NSString *name, NSString *pwd){
        NSLog ( @"%@ %@" ,name,pwd);
        // 判断用户名和密码是否同时存在,需要转换成 NSNumer 类型,才能被当做 id 传递
        return @( name. length > 0 && pwd. length > 0) ;
    }] subscribeNext :^( id   _Nullable x) {
        NSLog ( @"%@" ,x);
       
        @ strongify ( self );
        self . btn . enabled = [x boolValue ];
//        weakSelf.btn.enabled = [x boolValue];
    }];
}
// RAC 在使用的时候,因为系统提供的信号是始终存在的!因此 , 所有的 block , 如果出现 'self.' / ' 成员变量 ' 几乎百分之百会循环引用!
/**
  解除循环引用的方法
 1.__weak
 2. 利用 RAC 提供的 weak-strong dance
    block 的外部使用 @weakify(self)
    block 的内部使用 @strongify(self)
    然后,直接使用 self 即可。
 */
// 成员变量不好用 weak

RAC4-使用RAC实现响应式编程
什么是响应式编程?
例,我们有3个变量,b = 3,c = 4, a = b + c = 7,如果这时我们修改b的值,让b=100,这时a的值不会发生变化,响应式编程就是当修改b或c的值时,a的值也会跟着变化。
iOS 开发中,可以使用 KVO 监听对象的属性值,达到这一效果!
因为 苹果的 KVO 会统一调用同一个方法,方法是固定的,如果监听属性过多,方法非常难以维护!
RAC 是目前实现响应式编程的唯一解决方案!

使用MVVM实现双向绑定
MVVM:将“数据模型数据双向绑定”的思想作为核心,因此在View和Model之间没有联系,通过ViewModel进行交互,而且Model和ViewModel之间的交互是双向的,因此视图的数据的变化会同时修改数据源,而数据源数据的变化也会立即反应到View上。

代码实现
__weak typeof ( self )weakSelf = self ;
    // 双向绑定
    // 1> 模型( KVO 数据)绑定 UI text 属性)
    // a) name(string) -> text(string)
    RAC (nameTextField, text ) = RACObserve ( _person , name );
    NSLog ( @"RACObserve(_person, name):%@" , RACObserve ( _person , name ));
    // b) age(NSInteger) -> text(string) RAC 中传递的数据都是 id 类型
    // 如果使用基本数据类型绑定 UI 的内容,需要使用 map 函数,通过 block value 的数值进行转换之后,才能绑定
    RAC (ageTextField, text ) = [ RACObserve ( _person , age ) map :^ id _Nullable ( id   _Nullable value) {
        NSLog ( @"%@ %@" ,value,[value class ]);
       
        // 错误的转换, value 本身已经是 NSNumber, 需要字符串
//        return [NSString stringWithFormat:@"%zd",value];
        return [value description ];
    }];
   
    // 2> UI 绑定 模型
    [[ RACSignal combineLatest : @[ nameTextField. rac_textSignal ,ageTextField. rac_textSignal ] ] subscribeNext :^( RACTuple * _Nullable x) {
       
        weakSelf. person . name = [x first ];
        weakSelf. person . age = [[x second ] integerValue ];
    }];
   
    // 3> 添加按钮,输出结果
    UIButton *btn = [ UIButton buttonWithType : UIButtonTypeContactAdd ];
    btn. center = self . view . center ;
    [ self . view addSubview :btn];
   
    [[btn rac_signalForControlEvents : UIControlEventTouchUpInside ] subscribeNext :^( __kindof UIControl * _Nullable x) {
        // 循环引用!!!
        NSLog ( @"_person.name:%@  _person.age:%zd" ,weakSelf. person . name ,weakSelf. person . age );
    }];

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值