ReactiveCocoa 概述 RAC 提高OC 开发效率「建议收藏」

ReactiveCocoa 概述 RAC 提高OC 开发效率「建议收藏」响应式编程:不需要考虑调用顺序,只需要知道考虑结果, 即一个改变就会使结果改变.典型例子: aView 上添加的view, 当aView 约束发

欢迎大家来到IT世界,在知识的湖畔探索吧!

ReactiveCocoa 概述》
《RACSignal》
《RACDisposable》
《RACSubject、RACReplaySubject(内附冷信号和热信号的区别)》
《集合RACTuple、RACSequence》
《RAC 中的通知、代理、KVO, 基本事件、方法的监听》
《rac_liftSelector》
《RACMulticastConnection》
《RACCommand》
《RAC – 核心方法bind》
《RAC – 定时器》
《RACScheduler》
《RAC – 点击获取验证码 demo》
《RAC – 映射(Map & flattenMap)》
《RAC信号操作解释合集》
《RAC – 信号的生命周期》

1. 简介

ReactiveCocoa (简称为RAC)是由Github开源的一个应用于iOS和OS开发的新框架, 是基于响应式编程思想的Objective-C的实践, Cocoa则是苹果整套框架的简称.

2. 编程思想

结合了以下两种编程风格:

函数式编程(Functional Programming):

把操作尽量写成一系列嵌套的函数或者方法调用.每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果), 即每一步都需要有结果.

响应式编程(Reactive Programming):

不需要考虑调用顺序,只需要知道考虑结果, 即一个改变就会使结果改变.典型例子(AutoLayout): aView 上添加的view, 当aView 约束发生变化时, 的view 也会随之改变.

所以, ReactiveCocoa被描述为函数响应式编程框架.

3. 如何导入ReactiveCocoa框架

通常都会使用CocoaPods导入,
PS: iOS-Cocoapods 的正确安装姿势

注意: (大小写字母一点不要写错)

ReactiveObjC — 对应的是RAC的OC版本
ReactiveCocoa–对应的是RAC的swift版本

3.1 纯OC项目

pod "ReactiveObjC"

欢迎大家来到IT世界,在知识的湖畔探索吧!

3.2 OC和Swift的混合项目

欢迎大家来到IT世界,在知识的湖畔探索吧!  pod "ReactiveObjC"
   
  pod "ReactiveCocoa"
   
  pod "ReactiveObjCBridge"

3.3 纯Swift项目

pod "ReactiveCocoa"

4. ReactiveCocoa常见类

ReactiveCocoa 概述 RAC 提高OC 开发效率「建议收藏」

注: 该图片来源于网络.

.End

————–

RACSignal: 信号类, 本身不具备发送信号的能力, 当被订阅后, 用于传递改变的数据

ReactiveCocoa 概述 RAC 提高OC 开发效率「建议收藏」

  • sendNext(id):可理解为传递正确数据,告诉订阅者进行下一步处理
  • sendError:传递的数据错误,告诉订阅者错误处理
  • sendCompleted:告诉订阅者已完成
欢迎大家来到IT世界,在知识的湖畔探索吧!- (void)signalTest {        /* 1. 创建信号 signal            - 通过 createSignal: 方法创建,其参数为一个返回值位RACDisposable 类型的block (didSubcribe)     */    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {                // 2. 通过block 传入的订阅者subscriber 来发送信息        [subscriber sendNext:@10];                // sendError、sendCompleted 二者只能发送其一,  就代表结束了.        [subscriber sendError:[NSError errorWithDomain:@"错误" code:1001 userInfo:nil]];        [subscriber sendCompleted];        // 这里需要返回一个RACDisposable 类型的对象, 用于提前结束订阅等操作, 一般无特殊需求, 返回nil 即可.        return nil;    }];        /* 3. 订阅者 (subscriber)            - subscribeNext + error + completed 组合起来就是订阅者            - 一旦订阅者订阅了信号消息, 就会执行上面的didSubcribe 的block.     */    [signal subscribeNext:^(id  _Nullable x) {        NSLog(@"%@", x);    } error:^(NSError * _Nullable error) {        NSLog(@"ERROR=%@", error);    } completed:^{        NSLog(@"完成");    }];}

RAC 中的通知、代理、KVO, 基本事件、方法的监听

rac_addObserverForName(通知)

OC正常写法(需要自己单独实现noti: 方法去处理通知事件)

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti:) name:@"noti" object:nil];

OC 中使用RAC 写法

    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"noti" object:nil] subscribeNext:^(NSNotification * _Nullable x) {
          // 处理通知事件
    }];

↓rac_addObserverForName: 原理分析↓

- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object {
    @unsafeify(object);
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        @strongify(object);
        // 内部封装的 KVO
        id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) {
            // 发送监听信息
            [subscriber sendNext:note];
        }];

        return [RACDisposable disposableWithBlock:^{
            // 在取消订阅后, 移除观察者
            [self removeObserver:observer];
        }];
    }] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object];
}
  • 代理

RACSubject 通常用来代替代理
rac_signalForSelector 也可以用作代理, 但是无法传值!
这里只说RACSubject , rac_signalForSelector 在下文中有详细介绍.

  1. 首先创建一个RedView的类继承于UIView

RedView.h

#import <UIKit/UIKit.h>
#import <ReactiveObjC/ReactiveObjC.h>

NS_ASSUME_NONNULL_BEGIN

@interface RedView : UIView

// rac 实现代理
@property (nonatomic, strong)RACSubject *subject;

@end

NS_ASSUME_NONNULL_END

RedView.m

#import "RedView.h"

@implementation RedView

- (RACSubject *)subject{
    if(!_subject){
        _subject = [RACSubject subject];
    }
    return _subject;
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    [self.subject sendNext:@"红色视图"];
}

@end

控制器中代码:

#import "ViewController.h"
#import <ReactiveObjC/ReactiveObjC.h>
#import "RedView.h"

@interface ViewController ()

@property (nonatomic, strong)RedView *red;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    [self delegateTest];
}

- (void)delegateTest {
    
    self.red = [[RedView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    self.red.backgroundColor = [UIColor redColor];
    [self.view addSubview:self.red];
    [self.red.subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"代理: %@", x);
    }];
}
  • KVO

需要引入 #import <NSObject+RACKVOWrapper.h>头文件
方式一:

- (void)RAC_KVOTest1 {
    
    self.kvoView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    self.kvoView.backgroundColor = [UIColor redColor];
    [self.view addSubview:self.kvoView];
    
    [self.kvoView rac_observeKeyPath:@"frame" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
        NSLog(@"监听到最新frame - %@", value);
    }];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.kvoView.frame = CGRectMake(50, 50, 50, 50);
}

输出结果: 监听到最新frame – NSRect: {{50, 50}, {50, 50}}

方式二:

- (void)RAC_KVOTest2 {
    
    self.kvoView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    self.kvoView.backgroundColor = [UIColor redColor];
    [self.view addSubview:self.kvoView];
    
    [[self.kvoView rac_valuesForKeyPath:@"frame" observer:self] subscribeNext:^(id  _Nullable x) {
        // 刚开始运行, 就会先打印一遍原始值 -> 监听到最新frame - NSRect: {{100, 100}, {100, 100}}
        NSLog(@"监听到最新frame - %@", x);
        // 然后点击打印 -> 监听到最新frame - NSRect: {{50, 50}, {50, 50}}
    }];
}

方式三:

- (void)RAC_KVOTest3 {
    
    self.kvoView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    self.kvoView.backgroundColor = [UIColor redColor];
    [self.view addSubview:self.kvoView];
    
    // 使用RAC 封装的宏定义, 打印结果和方式2 一样
    [RACObserve(self.kvoView, frame) subscribeNext:^(id  _Nullable x) {
        NSLog(@"监听到最新frame - %@", x);
    }];
}

注意: 方式2、3中有注释提到, 在监听过程中会出现先打印一次原始数据值

  • rac_signalForSelector(监听方法)
- (void)rac_signalForSelectorTest {
    
    [[self rac_signalForSelector:@selector(touchesBegan:withEvent:)] subscribeNext:^(RACTuple * _Nullable x) {
        
        NSLog(@"监听屏幕点击方法: %@", x);
    }];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

}

也可以用作代理的使用, 比如检测上面RedView 的点击事件

    [[self.red rac_signalForSelector:@selector(touchesBegan:withEvent:)] subscribeNext:^(RACTuple * _Nullable x) {
        
        NSLog(@"控制器知道RedView被点击了");
    }];
  • rac_signalForControlEvents(监听事件)
- (void)rac_signalForControlEventsTest {
    
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(200, 200, 100, 50)];
    button.backgroundColor = [UIColor blueColor];
    [self.view addSubview:button];
    
    [[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        
        NSLog(@"按钮被点击了");
    }];
}
  • rac_textSignal(监听textfield输入)
- (void)rac_textSignalTest {
    
    UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(200, 200, 100, 50)];
    textField.layer.borderColor = [UIColor redColor].CGColor;
    textField.layer.borderWidth = 1;
    textField.layer.cornerRadius = 5;
    textField.clipsToBounds = YES;
    [self.view addSubview:textField];
    [textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        // x:是最新文本数据
        NSLog(@"%@", x);
    }];
}
  • RAC 的宏
RAC(<#TARGET, ...#>)

RAC(对象,对象的属性) = (一个信号);
比如:RAC(button, enable) = (RACSignal) 按钮的enable 等于一个信号

.End

RACSubject、RACReplaySubject(内附冷信号和热信号的区别)

  • RACSubject

RACSignal 是冷信号, 不能够自己发送信号, 需要订阅者订阅, 特点是确定未来, 也就是知道什么时候结束/终止, 无视订阅者, 不管谁订阅, 都是从头开始执行一段老代码

帮助理解: 冷信号 相当于 剧本, 当订阅者订阅时, 就相当于开始拍戏, 不管谁订阅, 都是从头开始拍, 拍完了也就结束了.

RACSubject 继承自RACSignal, 是热信号, 也就是说既可以充当信号,也可以发送信号, 并不确定什么时候终止, 关心订阅者, 先来先得、后来少得

帮助理解: 热信号 相当于 拍好的戏, 当订阅者订阅时, 就开始演戏, 接下来再有订阅者订阅, 演到哪里就继续演, 不会重新从头开始, 即先来的看得多、后来看的就少.

代码分析:

    // 1.创建信号
    RACSubject *subject = [RACSubject subject];
    
    // 2.订阅信号
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"x = %@", x);
    }];
    
    // 3.发送数据
    [subject sendNext:@10];

↓[RACSubject subject] 内部实现↓

+ (instancetype)subject {
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (self == nil) return nil;

    // 创建一个 复合的_disposable 对象, 用于取消订阅
    _disposable = [RACCompoundDisposable compoundDisposable];
    // 创建一个 _subscribers 订阅者集合, 用来保存订阅者
    _subscribers = [[NSMutableArray alloc] initWithCapacity:1];
    
    return self;
}

↓ 订阅信号 subscribeNext: 内部实现↓
其实内部也RACSignal 信号订阅大致相同, 唯一不同的是subscribe: 方法的实现:

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    NSMutableArray *subscribers = self.subscribers;
    @synchronized (subscribers) {
        // 注意: 每次订阅, 都将订阅者添加至 信号内部的_subscribers 数组中
        [subscribers addObject:subscriber];
    }
    
    [disposable addDisposable:[RACDisposable disposableWithBlock:^{
        @synchronized (subscribers) {
            // Since newer subscribers are generally shorter-lived, search
            // starting from the end of the list.
            NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
                return obj == subscriber;
            }];

            if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
        }
    }]];

    return disposable;
}

↓ 发布消息sendNext: 内部实现↓

- (void)sendNext:(id)value {
    // 遍历_subscribers 集合, 向每一个订阅者发送sendNext: 消息, 即所有订阅者依次发送消息
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
    }];
}

总结

创建的subject的是内部会创建一个数组_subscribers用来保存所有的订阅者订阅信息的时候会创建订阅者,并且保存到数组中遍历subject中_subscribers中的订阅者,依次发送信息

注意

RACSubject 可以被订阅多次,并且只能是先订阅后发布, 因为:先发送, 再有订阅者, 订阅者收不到订阅之前的消息, 所以称之为先来先得, 晚来少得

  • RACReplaySubject
    针对RACSubject 的注意点, 偏偏要先发送消息, 再去订阅信号, 该怎么办呢???

这里就可以使用RACReplaySubject , 它继承自RACSubject, 目的就是来解决先发送信号后订阅的问题.

代码分析: 和RACSubject 的使用一毛一样

    // 先发送信号
    [replaySubject sendNext:@10];
    
    // 后订阅信号
    [replaySubject subscribeNext:^(id  _Nullable x) {
        // 可以正常打印
        NSLog(@"x = %@", x);
    }];

↓具体实现原理↓

RACReplaySubject 对象创建的时候, 会在父类的基础之上多做一步,创建一个数组用来保存发送的数据(当_subscribers 中所有订阅者都成功发送了数据, 那么就会删除当前要发送的数据, 避免出现一个数据重复发送的问题)发送数据: 当有订阅者订阅时,发送数据; 没有订阅者订阅时发送失败, 就不发送,等待新的订阅者.订阅信号, 先遍历一次保存数据的数组, 如果有就执行第二步

.End

作者:下班不写程序
来源:简书

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/17541.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信