iOS - ReactiveCocoa

1. ReactiveCocoa 简介

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

2. ReactiveCocoa 作用

  • 在我们iOS开发过程中,当某些事件响应的时候,需要处理某些业务逻辑,这些事件都用不同的方式来处理。

  • 比如按钮的点击使用 action,使用 delegate,属性值改变使用 KVO,通知等系统提供的方式,都可以通过RAC处理。

  • ReactiveCocoa 为事件提供了很多处理方法,而且利用 RAC 处理事件很方便,可以把要处理的事情和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高内聚,低耦合的思想。

3. ReactiveCocoa 编程思想

ReactiveCocoa 结合了几种编程风格:

  • 函数式编程(Functional Programming)

  • 响应式编程(Reactive Programming)

所以,ReactiveCocoa 被描述为函数响应式编程(FRP)框架。

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

4. 编程思想

编程思想:通过开发需求,逐渐形成快速完成这些需求的思想。

4.1 面向过程:处理事情以过程为核心,一步一步的实现。
4.2 面向对象:万物皆对象。
4.3 链式编程思想:是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3)
  • 链式编程特点:方法的返回值是 block,block 必须有返回值(本身对象),block 参数(需要操作的值)

  • 代表:Masonry 框架。

4.4 响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。
  • 代表:KVO运用。
4.5 函数式编程思想:是把操作尽量写成一系列嵌套的函数或者方法调用。
  • 函数式编程本质:就是往方法中传入 Block,方法中嵌套 Block 调用,把代码聚合起来管理。

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

  • 代表:ReactiveCocoa。

5. 如何导入ReactiveCocoa框架

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

注意:

podfile 如果只描述 pod ‘ReactiveCocoa’,会导入不成功。

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

  • Objective-C 版的 ReactiveCocoa (下面简称 RAC )集成方法
use_frameworks!
pod 'ReactiveObjC' 或者 pod 'ReactiveObjC', '~> 指定版本'
  • Swift 版的 ReactiveCocoa 集成方法
use_frameworks!
pod 'ReactiveCocoa' 或者pod 'ReactiveCocoa', '~> 指定版本'

6. ReactiveCocoa 常见类

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

  • RACSiganl:信号类,表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。
  • 信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。
  • 默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。
  • 如何订阅信号:调用信号 RACSignal 的 subscribeNext 就能订阅。
RACSiganl 简单使用:

RACSignal 使用步骤:

  1. 创建信号:

    • + (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe
      
  2. 订阅信号,才会激活信号:

    • - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
      
  3. 发送信号:

    • - (void)sendNext:(id)value
      
#pragma mark - RACSignal
- (void)RACsignal {
    // 三步骤:创建信号,订阅信号,发送信号
    
    // 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        // 3.发送信号
        [subscriber sendNext:@1];
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"默认信号发送完毕被取消");
        }];
    }];

    // 2.订阅信号
    RACDisposable *disposable = [signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    // 取消订阅
    [disposable dispose];
}

结果如下:


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

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

    • 使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。
  • RACSubject:RACSubject 是信号提供者,自己可以充当信号,又能发送信号。

    • 使用场景:通常用来代替代理,有了它就不必要定义代理了。
  • RACReplaySubject:重复提供信号类,RACSubject 的子类。

RACReplaySubject 与 RACSubject 区别:

RACReplaySubject 可以先发送信号,再订阅信号,RACSubject 就不可以。

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

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

RACSubject 简单使用:

RACSubject 使用步骤

  1. 创建信号 [RACSubject subject],跟 RACSiganl 不一样,创建信号时没有 block。

  2. 订阅信号:

    • - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
      
  3. 发送信号:

    • sendNext:(id)value
      
#pragma mark - RACSubject
- (void)RACSubject {
    // 信号提供者,自己可以充当信号,又能发送信号。
    
    // 创建信号
    RACSubject *subject = [RACSubject subject];
    
    // 订阅信号
    [subject subscribeNext:^(id x) {
        NSLog(@"订阅者一%@",x);
    }];
    [subject subscribeNext:^(id x) {
        NSLog(@"订阅者二%@",x);
    }];

    // 发送信号
    [subject sendNext:@"111"];
}

结果如下:


RACReplaySubject 的简单使用

RACReplaySubject 使用步骤:

  1. 创建信号 [RACSubject subject],跟 RACSiganl 不一样,创建信号时没有 block。

  2. 可以先订阅信号,也可以先发送信号。

    1. 订阅信号

      • (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
    2. 发送信号:

      • sendNext:(id)value
        
#pragma mark - RACReplaySubject
- (void)RACReplaySubject {
    // 创建信号
    RACReplaySubject *replaySubject = [RACReplaySubject subject];

    // 发送信号
    [replaySubject sendNext:@"222"];
    [replaySubject sendNext:@"333"];

    // 订阅信号
    [replaySubject subscribeNext:^(id x) {
        NSLog(@"第一个订阅者%@",x);
    }];

   // 如果想一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。
    [replaySubject subscribeNext:^(id x) {
        NSLog(@"第二个订阅者%@",x);
    }];
}

结果如下:


  • RACTuple:元组类,类似 NSArray,用来包装值。
  • RACSequence:RAC 中的集合类,用于代替 NSArray,NSDictionary,可以使用它来快速遍历数组和字典。
RACSequence 和 RACTuple 简单使用
- (void)ArrayTuple {
    NSArray *array = @[@1,@2,@3];
    [array.rac_sequence.signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
}

结果如下:

- (void)DictionaryTuple {
    NSDictionary *dict = @{@"name":@"张三",@"age":@"20",@"sex":@"男"};
    [dict.rac_sequence.signal subscribeNext:^(id x) {
        // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
        // RACTupleUnpack 这是个宏,后面会介绍
        RACTupleUnpack(NSString *key,NSString *value) = x;
        NSLog(@"%@---%@",key,value);
        /*
         相当于:
         NSString *key = x[0];
         NSString *value = x[1];
         */
    }];
}

结果如下:

使用场景:字典转模型

- (void)DictionaryToModel {
    // RAC高级写法:
    [AFNHelper get:kUrl parameter:nil success:^(id responseObject) {
        NSArray *array = responseObject;
        // map:映射的意思,目的:把原始值 value 映射成一个新值
        // array: 把集合转换成数组
        // 底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。
      NSArray *musicArray =[[array.rac_sequence map:^id(id value) {
          Music *music = [Music fetchMusicModelWithDict:value ];
            return music;
        }] array];
        NSLog(@"--------%@",musicArray);
    } faliure:^(id error) {
        NSLog(@"%@",error);
    }];
}

  • RACMulticastConnection:用于当一个信号被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的 block造成副作用,可以使用这个类处理。
  • 使用注意:RACMulticastConnection 通过 RACSignal 的 -publish 或者 -muticast: 方法创建。
RACMulticastConnection简单使用

需求:假设在一个信号中发送请求,每订阅一次都会发送请求,这样就会导致多次请求。
解决:使用 RACMulticastConnection 就能解决.

RACMulticastConnection使用步骤:

  1. 创建信号:

    • + (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe
      
  2. 创建连接:

    • RACMulticastConnection *connect = [signal publish];
      
  3. 订阅信号:

    • 注意:订阅的不再是之前的信号,而是连接的信号。

      • [connect.signal subscribeNext:nextBlock]
    • 连接

      • [connect connect]
  4. 发送信号

- (void)RACMulticastConnection {
    // 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
       NSLog(@"请求一次");
       //5.发送信号
       [subscriber sendNext:@"2"];
       return nil;
    }];
    // 2.把信号转化为连接类
    RACMulticastConnection *connection = [signal publish];
    // 3.订阅连接类信号
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"第一个订阅者%@",x);
    }];
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"第二个订阅者%@",x);
    }];
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"第三个订阅者%@",x);
    }];

    // 4.链接信号
    [connection connect];
}

结果如下:


  • RACCommand:RAC 中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。 >
  • 使用场景:监听按钮点击,网络请求。 >
RACCommand 简单使用
一、RACCommand使用步骤:
  1. 创建命令:

    • - (instancetype)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],这时命令才会执行完毕,否则永远处于执行中。

三、RACCommand 设计思想:内部 signalBlock 为什么要返回一个信号,这个信号有什么用。

1.在 RAC 开发中,通常会把网络请求封装到 RACCommand,直接执行某个 RACCommand 就能发送请求。

2.当 RACCommand 内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过 signalBlock 返回的信号传递了。

四、如何拿到 RACCommand 中返回信号发出的数据。
  1. RACCommand 有个执行信号源 executionSignals,这个是 signal of signals (信号的信号),意思是信号发出的数据是信号,不是普通的类型。

  2. 订阅 executionSignals 就能拿到 RACCommand 中返回的信号,然后订阅 signalBlock 返回的信号,就能获取发出的值。

- (void)command {
    // 1.创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        // 命令内部传递的参数
        NSLog(@"input===%@",input);
        // 2.返回一个信号,可以返回一个空信号 [RACSignal empty];
        return [RACSignal createSignal:^RACDisposable *(id subscriber) {
            NSLog(@"发送数据");
            // 发送信号
            [subscriber sendNext:@"22"];
            // 注意:数据传递完,最好调用 sendCompleted,这时命令才执行完毕。
            [subscriber sendCompleted];
            return nil;
        }];
    }];

    // 拿到返回信号方式二:
    // command.executionSignals 信号中的信号 switchToLatest 转化为信号
    [command.executionSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"拿到信号的方式二%@",x);
    }];

    // 拿到返回信号方式一:
    RACSignal *signal =  [command execute:@"11"];

    [signal subscribeNext:^(id x) {
        NSLog(@"拿到信号的方式一%@",x);
    }];

    // 3.执行命令
    [command execute:@"11"];

    // 监听命令是否执行完毕
    [command.executing subscribeNext:^(id x) {
        if ([x boolValue] == YES) {
            NSLog(@"命令正在执行");
        }
        else {
            NSLog(@"命令完成/没有执行");
        }
    }];
}

结果如下:

7.ReactiveCocoa 常见宏

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

基本用法:

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

结果如下:

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

基本用法:

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

结果如下:

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

基本用法:

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

结果如下:

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

基本用法

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

结果如下:

8.ReactiveCocoa 开发中常见用法

  • 代替代理:
    • rac_signalForSelector:用于代替代理。

需求:在使用数组下标为 3 的值前做额外的操作。

NSArray *array = @[@"1",@"2",@"3",@"4",@"5"];
    [[array rac_signalForSelector:@selector(objectAtIndex:)]
    subscribeNext:^(id x) {
        NSLog(@"做额外操作");
    }];
    NSLog(@"NSArray index_3:%@",[array objectAtIndex:3]);

结果如下:


  • 代替KVO :
    • rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。
[[self.blueView rac_valuesAndChangesForKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew observer:self] subscribeNext:^(RACTwoTuple<id,NSDictionary *> * _Nullable x) {
        NSLog(@"%@",x);
    }];
    [self.blueView setBackgroundColor:[UIColor redColor]];

结果如下:


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

把按钮点击事件转换为信号,点击按钮,就会发送信号

// 监听事件
[[self.bt rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    NSLog(@"点击了按扭");
}];

结果如下:


  • 监听文本框文字改变:
    • rac_textSignal:只要文本框发出改变就会发出这个信号。
      使用 @weakify(self); 和 @strongify(self); 解决循环引用问题。
// 监听文本框的文字改变
    @weakify(self);
    [self.textField.rac_textSignal subscribeNext:^(id x) {
        @strongify(self);
        NSLog(@"%@",x);
        self.labelView.text = x;
    }];

结果如下:

源码地址:ReactiveCocoaDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容