zoukankan      html  css  js  c++  java
  • RAC(ReactiveCocoa)概括

    ReactiveCocoa(简称RAC,以下都用RAC)是github团队开源的一套基于Cocoa并且具有FRP(Functional Reactive Programming-响应式编程)特性的框架。RAC本身就是一个第三方类库,使用它可以大大提高开发效率,简化代码,目前在各个公司也在大范围使用。RAC比较复杂,在正式介绍之前,先看一下它的类图,以便大致了解层次结构。

    RAC主要包含了四个组件

    • 信号源方面:RACStream及其子类
    • 订阅者方面:RACSubscriber及其子类
    • 调度器方面:RACScheduler及其子类
    • 清洁工方面:RACDisposable及其子类

    在RAC中,信号源是最核心的部分,其工作过程是:创建信号--订阅信号--发送信号

     拓展:响应式编程(FRP)

    在命令式编程中,a = b + c代表是b与c的加和结果赋值给a,如果之后再改变b或者c的值并不会影响a。但是在响应式编程中,a的值会随着b或者c的变化而变化,也就是a的结果和b与存在绑定关系,b或者c的变化会直接影响a。这就是响应式编程(FRP),举个简单的例子。

    信号源

    信号分为冷信号和热信号。

    理解冷信号和热信号的区别对RAC的理解有非常大的帮助,下面我们重点讲解这:

    • Hot Observable(热信号)是主动的,即使你没有订阅信号,它也会时可推送,例如鼠标移动;而Cold Observable(冷信号)是被动的,也就是只有你订阅信号,它才会发布消息,反之不然。
    • Hot Observable(热信号)可以有多个订阅者,是一对多,整个集合可以与订阅者共享信息;而Cold Observable只能一对一,遇到有不同的订阅者,消息是重新完整发送的。

    在RAC中除了RACSubject和其子类是热信号,剩下的就是冷信号。RACSubject和其子类类似直播,错过之后也就不会处理了;而signal类似点播,每次发送订阅,都是从头开始。

    Subject具备如下特点:

    • Subject是非RAC到RAC的桥梁
    • Subject可以附加行为:RACReplaySubject具备为订阅者缓冲事件的能力。

     为了大家更好理解两者的区别,如下:

    //创建热信号
         RACSubject *subject = [RACSubject subject];
        [subject sendNext:@1]; //立即发送1 
        [[RACScheduler mainThreadScheduler] afterDelay:0.5 schedule:^{
            [subject sendNext:@2]; //0.5秒后发送2 }];
    
        [[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{
            [subject sendNext:@3]; //2秒后发送3 }];
        [[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{
            [subject subscribeNext:^(id x) {
                 NSLog(@"subject1接收到了%@",x); //0.1秒后subject1订阅了 }];
        }];
        [[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
            [subject subscribeNext:^(id x) {
                 NSLog(@"subject2接收到了%@",x); //1秒后subject2订阅了 }];
        }];
    //创建冷信号 
        RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
            [subscriber sendNext:@1];
            [[RACScheduler mainThreadScheduler] afterDelay:0.5 schedule:^{
                [subscriber sendNext:@2];
            }];
            [[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{
                [subscriber sendNext:@3];
            }]; return nil;
        }];
    
        [[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{
            [signal subscribeNext:^(id x) { NSLog(@"signal1接收到了%@", x);
            }];
        }];
        [[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
            [signal subscribeNext:^(id x) { NSLog(@"signal2接收到了%@", x);
            }];
        }]; 

    通过运行结果:

    从上面运行结果发现:

    • 0.1秒后订阅的subject1接收到了0.5秒后2秒后发送的信号,没有接收到之前发送的新号。

    • 1秒后订阅的subject2接收到了2秒后发送的信号,也没有接收到之前发送的新号。

    • signal1和signal2都接收到了所有信号。

    从上面的运行结果总结

    热信号是主动的,即使没有订阅事件,仍然会时刻推送;而冷信号是被动的,只有当你订阅的时候,它才会发送消息。

    热信号是可以有多个订阅者,一对多,信号是可以与订阅者相互共享信息的。在第一段代码,两个订阅者是共享的,他们在同一时间接收到3个值,而冷信号只能一对一,当有不同的订阅者,消息都会从新完整发送。

    使用信号常见的问题:

    1.多次订阅

    对RAC的信号进行转换的时候,其实就是对原有的信号进行订阅从而产生新的信号。如下代码所示:

    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { NSLog(@"来了"); //网络请求,产生model [subscriber sendNext:model]; return nil;
        }];
        
        RACSignal *name = [signal flattenMap:^RACStream *(Person *model) { return [RACSignal return:model.name];
        }];
        RACSignal *age = [signal flattenMap:^RACStream *(Person *model) { return [RACSignal return:model.age];
        }];
    
        RAC(self.userNameTextFiled,text) = [[name catchTo:[RACSignal return:@"error"]] startWith:@"name:"];
        RAC(self.passwordTextField,text) = [[age catchTo:[RACSignal return:@"error"]] startWith:@"age:"];

    上面分别对model进行了map,也就是产生了两个新的信号,然后再对两个信号进行订阅,对这两个信号订阅的时候,也会对间接对原信号进行订阅,从而造成对原信号的多次订阅,如上所示来了就输出了三次,如果是网络请求的话,也会输出三次,所以一定在信号转换的时候一定要注意这些情况。

    RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) { NSLog(@"来了");
            [subscriber sendNext:model]; return nil;
        }] replayLazily]; //转换为热信号 
        RACSignal *name = [signal flattenMap:^RACStream *(Person *model) { return [RACSignal return:model.name];
        }];
        RACSignal *age = [signal flattenMap:^RACStream *(Person *model) { return [RACSignal return:model.age];
        }];
    
        RAC(self.userNameTextFiled,text) = [[name catchTo:[RACSignal return:@"error"]] startWith:@"name:"];
        RAC(self.passwordTextField,text) = [[age catchTo:[RACSignal return:@"error"]] startWith:@"age:"];

    2、内存泄露

    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { //1 Person *model = [[Person alloc] init];
            [subscriber sendNext:model];
            [subscriber sendCompleted]; return nil;
        }]; self.flattenMapSignal = [signal flattenMap:^RACStream *(Person *model) { //2 return RACObserve(model, name);
        }];
        [self.flattenMapSignal subscribeNext:^(id x) { //3 NSLog(@"recieve - %@", x);
        }];

    如上代码,看起来工作正常,但你使用内存检测工具会发现,这里会造成内存泄漏,原因就是

    #define RACObserve(TARGET, KEYPATH)  ({ 
            _Pragma("clang diagnostic push") 
            _Pragma("clang diagnostic ignored "-Wreceiver-is-weak"") 
            __weak id target_ = (TARGET); 
            [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; 
            _Pragma("clang diagnostic pop") 
        })

    这段代码,所以这里的Block引用了self,就造成了循环引用。
    解决办法也很简单,使用@weakify和@strongify即可:

    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { 
            Person *model = [[Person alloc] init];
            [subscriber sendNext:model];
            [subscriber sendCompleted]; return nil;
        }];
        @weakify(self); self.flattenMapSignal = [signal flattenMap:^RACStream *(Person *model) {
            @strongify(self); return RACObserve(model, name);
        }];
        [self.flattenMapSignal subscribeNext:^(id x) { NSLog(@"recieve - %@", x);
        }];

    调度器:RACScheduler 在 ReactiveCocoa 中就是扮演着调度器的角色,本质上,它就是用 GCD 的串行队列来实现的,并且支持取消操作。是的,在 ReactiveCocoa 中,并没有使用到 NSOperationQueue 和 NSRunloop 等技术,RACScheduler 也只是对 GCD 的简单封装而已。

    清洁工:RACDisposable 在 ReactiveCocoa 中就充当着清洁工的角色,它封装了取消和清理一次订阅所必需的工作。它有一个核心的方法 -dispose ,调用这个方法就会执行相应的清理工作,这有点类似于 NSObject 的 -dealloc 方法。

    以后博客也将继续介绍RAC的基本框架。

  • 相关阅读:
    坦克大战
    java多线程应用场景
    java中的多线程(资料)
    设置线程名
    线程名称的设置及取得
    java调试
    文件上传细节处理
    Servlet生命周期
    java的动态绑定与静态绑定
    Mysql 连接池调用完成后close代理方法引出的设计模式
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/10419156.html
Copyright © 2011-2022 走看看