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

来源:互联网 发布:淘宝买家秀招聘 编辑:程序博客网 时间:2024/05/23 20:14
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实现监听,订阅者收到相应的方法时,获取自己想要的东西,并作出相应的操作。

Demo地址
https://github.com/CoderXAndZ/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 [RACSignalcreateSignal:^RACDisposable * _Nullable(id<RACSubscriber_Nonnull subscriber) {

       //发送不同的信号
       _personList = [NSMutableArrayarray];
       
       //模拟异步加载数据
       dispatch_async(dispatch_get_global_queue(0,0), ^{
           
           //模拟延时
            [NSThreadsleepForTimeInterval:1.0];
           
           //创建数据
           for (NSInteger i =0; i <20; i++) {
               XZPerson *person = [[XZPersonalloc]init];
               
                person.name = [@"zhangsan - "stringByAppendingFormat:@"%ld",(long)i];
                person.age = 15 +arc4random_uniform(20);
               
                [_personListaddObject:person];
            }
           
           NSLog(@"%@",_personList);
           
           //完成回调发送信号给订阅者,主线程
           dispatch_async(dispatch_get_main_queue(), ^{
               BOOL isError =NO;
               if (isError) {
                    [subscribersendError:[NSErrorerrorWithDomain:@"cn.xzproject.error"code:1001userInfo:@{@"error message":@"异常错误"}]];
                }else {
                    [subscribersendNext:_personList];
                }
               
               //发送完成事件
                [subscribersendCompleted];
               
            });
        });
       returnnil;
    }];

}

ViewController.m

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

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

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

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

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

组合信号
创建两个UITextField,将这两个的输入框的信号进行组合
UITextField *nameTextField = [[UITextFieldalloc]initWithFrame:CGRectMake(20,40,300,40)];
    nameTextField.borderStyle = UITextBorderStyleRoundedRect;
    [self.viewaddSubview:nameTextField];
   
   UITextField *pwdTextField = [[UITextFieldalloc]initWithFrame:CGRectMake(20,100,300,40)];
    pwdTextField.borderStyle = UITextBorderStyleRoundedRect;
    [self.viewaddSubview:pwdTextField];
   
   //组合信号Tuple是元组,
    [[RACSignalcombineLatest:@[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,[xclass]);
       //打印结果===nameWertyui pwd3456yui [x class]:RACTuple
    }];

当有多个控件的话,使用combineLatest:可能会很麻烦,我们可以使用combineLatest:reduce:监听组合输入框事件
// reduce ->减少的意思,合并两个信号的数据,进行汇总计算时使用的!
   // id是返回值,参数是有括号的;
   // reduce中,可以通过接收的参数进行计算,并且返回需要的数值!例:登录界面,只有用户名和密码同时存在,才允许登录!
   
   //方法一:使用__weak避免循环引用
//    __weak typeof(self)weakSelf = self;
   //方法二:
    @weakify(self);
    [[RACSignalcombineLatest:@[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上。

代码实现
__weaktypeof(self)weakSelf = self;
   //双向绑定
   // 1>模型(KVO数据)绑定 UItext属性)
   // 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,[valueclass]);
       
       //错误的转换,value本身已经是 NSNumber,需要字符串
//        return [NSString stringWithFormat:@"%zd",value];
       return [valuedescription];
    }];
   
   // 2> UI绑定模型
    [[RACSignalcombineLatest:@[nameTextField.rac_textSignal,ageTextField.rac_textSignal]]subscribeNext:^(RACTuple * _Nullable x) {
       
        weakSelf.person.name = [x first];
        weakSelf.person.age = [[x second]integerValue];
    }];
   
   // 3>添加按钮,输出结果
   UIButton *btn = [UIButtonbuttonWithType:UIButtonTypeContactAdd];
    btn.center = self.view.center;
    [self.viewaddSubview:btn];
   
    [[btnrac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(__kindofUIControl *_Nullable x) {
       //循环引用!!!
       NSLog(@"_person.name:%@  _person.age:%zd",weakSelf.person.name,weakSelf.person.age);
    }];

原创粉丝点击