美文网首页
ReactiveCocoa综合整理

ReactiveCocoa综合整理

作者: th先生 | 来源:发表于2020-04-09 11:06 被阅读0次

    ReactiveCocoa(其简称为 RAC)是Github开源的一个应用于 iOS 和 OS X 开发的框架。RAC 具有函数式编程和响应式编程的特性。
    我们都使用Cocoapods集成RAC,需要注意的是Podfile文件中必须使用user_framework!

    扩展1.

    函数式编程主要就是编程中所有过程可控,尤其在 js 这种没有原则的语言中,过程可控制尤为重要。
    函数式编程是由一系列只对参数做转换的纯函数拼接起来构成主体,两头再接输入输出。类似下图:


    lc.png

    这个程序运行时接受一个参数,就会输出一个结果。
    如果用户不断输入新参数,然后运行时(runtime)就在参数发生变化时自动触发当中这个转换逻辑运行,然后运行时再用得到的新结果去修改输出显示。

    纯函数:

    什么是纯函数呢?纯函数有三个重要的点:

    1. 函数的结果只受函数参数影响。
    2. 函数内部不使用能被外部函数影响的变量。
    3. 函数的结果不影响外部变量。

    不可变:

    不可变,顾名思义,就是变量或者结构在定义之后不能再发生值的变动,所有操作只是产生新的值而不是去覆盖之前的变量。这样去控制数据,能够让数据流动更加可控。

    扩展2.

    响应式编程(反应式编程)
    响应式编程就是异步数据流编程。咱们常见的单击事件就是一个异步事件流,你可以观察这个流,也可以基于这个流做一些自定义操作。一个流就是一个将要发生的以时间为序的事件序列。它能发射出三种不同的东西:一个数据值(data value)(某种类型的),一个错误(error)或者一个“完成(completed)”的信号。我们只能异步捕捉被发出的事件,使得我们可以在发出一个值事件时执行一个函数,发出错误事件时执行一个函数,发出完成事件时执行另一个函数。有时候你可以忽略后两个事件,只需聚焦于如何定义和设计在发出值事件时要执行的函数。

    适合用 RAC的场景:
    1、UI 操作,连续的动作与动画部分,例如某些控件跟随滚动。
    2、网络库,因为数据是在一定时间后才返回回来,不是立刻就返回的。
    3、刷新的业务逻辑,当触发点是多种的时候,业务往往会变得很复杂,用 delegate、notification、observe 混用,难以统一。这时用 RAC 可以保证上层的高度一致性,从而简化逻辑上分层。

    关于调试,
    1、RAC 源码下有 instruments 的两个插件,方便大家使用。
    signalEvents这个可以看到流动的信号的发出情况,对于时序的问题可以比较好的解决。
    diposable 可以检查信号的 disposable 是否正常

    ts.png
    2、打印别名给信号一个名字,然后通过下面的打印方法来进行调试
    /// Additional methods to assist with debugging.
    @interface RACSignal (Debugging)
    
    /// Logs all events that the receiver sends.
    - (RACSignal *)logAll;
    
    /// Logs each `next` that the receiver sends.
    - (RACSignal *)logNext;
    
    /// Logs any error that the receiver sends.
    - (RACSignal *)logError;
    
    /// Logs any `completed` event that the receiver sends.
    - (RACSignal *)logCompleted;
    

    增加log方法
    DExecute(({
    setenv("RAC_DEBUG_SIGNAL_NAMES", "RAC_DEBUG_SIGNAL_NAMES", 0);
    [signalUserGeo setNameWithFormat:@"signalUserGeo"];
    signalUserGeo = [signalUserGeo logAll];
    }));

    先理解信号RACSignal

    作为RAC中最为核心的一个类,信号可以理解为传递数据变化信息的工具,信号会在数据发生变化时发送事件流给它的订阅者,然后订阅者执行响应方法。信号本身不具备发送信号的能力,而是交给一个订阅者去发出。

    首先上一段代码,演示信号的一个基本使用。
    测试场景:我们要对一个用于输入用户名的UITextFiled进行检测,每次输入内容变化的时候都打出输入框的内容,使用RAC来实现此操作的关键代码如下:

    [self.userNameTxtField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
            NSLog(@"测试:%@",x);
    }];
    
    控制台打印:
    2018-03-23 17:57:00.497956+0800 ZSTest[4351:263810] 测试:1
    2018-03-23 17:57:00.498237+0800 ZSTest[4351:263810] 测试:12
    2018-03-23 17:57:00.498375+0800 ZSTest[4351:263810] 测试:123
    

    不使用代理方法,我们仅仅使用了一行方法就实现了对文本框输入内容的实时打印。
    其实RAC已经使用Category的形式为我们基本的UI控件创建了信号(如上例中的rac_textSignal),所以这里我们才可以很方便的实现信号订阅,而且订阅者在整个过程中也是对于我们隐藏的。

    注意:通知不建议使用RAC,详情了解:
    https://blog.csdn.net/qinqi376990311/article/details/79031581/

    下面是一个RACSignal被订阅的完整过程。

    //创建信号
    RACSignal *testSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        //1.订阅者发送信号内容
        [subscriber sendNext:@"发送信号内容"];
        //2.订阅者发送信号完成的信息,不需要再发送数据时,最好发送信号完成,可以内部调起清理信号的操作。
        [subscriber sendCompleted];
        //3.创建信号的Block参数,需要返回一个RACDisposable对象 ,可以返回nil。
        //RACDisposable对象用于取消订阅信号,此block在信号完成或者错误时调用。
        RACDisposable *racDisposable = [RACDisposable disposableWithBlock:^{
           NSLog(@"信号Error或者Complete时销毁");
        }];
        return racDisposable;
    }];
        
    //订阅信号
    RACDisposable *disposable = [testSignal subscribeNext:^(id  _Nullable x) {
        //新变化的值
        NSLog(@"订阅信号:subscribeNext:%@",x);
    } error:^(NSError * _Nullable error) {
        //信号错误,被取消订阅,被移除观察
        NSLog(@"订阅信号:Error:%@",error.description);
    } completed:^{
        //信号已经完成,被取消订阅,被移除观察
        NSLog(@"订阅信号:subscribeComplete");
    }];
    
    [disposable dispose];
    
    控制台打印:
    2018-03-23 17:57:00.497956+0800 ZSTest[4351:263810] 订阅信号:subscribeNext:发送信号内容
    2018-03-23 17:57:00.498237+0800 ZSTest[4351:263810] 订阅信号:subscribeComplete
    2018-03-23 17:57:00.498375+0800 ZSTest[4351:263810] 信号Error或者Complete时销毁
    

    当使用subscribeNext:error:completed:订阅信号时,隐式地创建了一个RACSubscriber对象.所以创建信号时使用的block所关联的对象会被订阅所持有。
    在RAC的内存关联中,一个重要的注意事项就是, 订阅会在 completion或是error时终止,订阅者也会被移除.
    这样,信号的生命周期也就会跟随事件流的逻辑生命周期,会有一些不会自行结束的信号存在,所以需要disposable存在.
    信号订阅的dispose操作,会移除所有关联的订阅者,而且也会释放该信号所占有的资源.

    通过源码来理解整个过程:

    1.创建信号

    创建信号,我们需要使用RACSignal的类方法createSignal。该方法需要一个Block作为参数。查看源码,我们就会发现RACSignal最终是通过调用自己子类RACDynamicSignalcreateSignal方法,将这个Block设置给了自己的didSubscribe属性的。

    //RACSignal.m文件
    + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
        return [RACDynamicSignal createSignal:didSubscribe];
    }
    
    //RACDynamicSignal.m文件
    + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
        RACDynamicSignal *signal = [[self alloc] init];
        signal->_didSubscribe = [didSubscribe copy];
        return [signal setNameWithFormat:@"+createSignal:"];
    }
    

    didSubscribe:这是创建信号时候需要传入的一个block,它的传入唯一一个参数是订阅者是id类型的subscriber,这个subscriber是必须遵循RACSubscriber协议的,而返回值是需要是一个RACDisposable对象。创建信号后的didSubscrib是一个等待执行的block。

    RACSubscriber:表示订阅者,创建信号时订阅者发送信号,这里的订阅者是一个协议而非一个类。信号需要订阅者帮助其发送数据。查看RACSubscriber的协议,我可以看到以下几个方法:

    //发送信息
    - (void)sendNext:(nullable id)value;
    //发送错误消息
    - (void)sendError:(nullable NSError *)error;
    //发送完成信息
    - (void)sendCompleted;
    //
    - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
    

    在创建一个信号的时候,订阅者使用sendNext发送信息。而且如果我们不再发送数据,最好在这里执行一次sendCompleted方法,这样的话,信号内部会自动调用对应的方法取消信号订阅。

    RACDisposable:这个类用于取消订阅信号和清理资源,在信号出现错误或者信号完成的时候,信号会自动调起RACDisposable对象的block方法。在代码中我们也可以看到,创建RACDisposable对象是使用disposableWithBlock方法设置了一个block操作,执行block操作之后,信号就不再被订阅了。

    2.订阅信号

    RACSignal调用subscribeNext方法,返回一个RACDisposable。

    - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock {
        NSCParameterAssert(nextBlock != NULL);
        NSCParameterAssert(errorBlock != NULL);
        NSCParameterAssert(completedBlock != NULL);
        
        RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock];
        return [self subscribe:o];
    }
    

    在此方法中,我们可以看到订阅信号有两个过程:
    过程1:使用subscribeNext的方法参数,创建出一个订阅者subscriber。
    过程2:信号对象执行了订阅操作subscribe,方法中传入参数是刚创建的订阅者。

    在这个方法中会新建一个RACSubscriber对象,并传入nextBlock,errorBlock,completedBlock。

    
    @interface RACSubscriber ()
     
    // These callbacks should only be accessed while synchronized on self.
    @property (nonatomic, copy) void (^next)(id value);
    @property (nonatomic, copy) void (^error)(NSError *error);
    @property (nonatomic, copy) void (^completed)(void);
    @property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;
    
    @end
    

    RACSubscriber这个类很简单,里面只有4个属性,分别是nextBlock,errorBlock,completedBlock和一个RACCompoundDisposable信号。

    + (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
     RACSubscriber *subscriber = [[self alloc] init];
       subscriber->_next = [next copy];
       subscriber->_error = [error copy];
       subscriber->_completed = [completed copy];
       return subscriber;
    }
    

    subscriberWithNext方法把传入的3个block都保存分别保存到自己对应的block中。这里涉及到block用copy修饰的问题。

    RACSignal调用subscribeNext方法,最后return的时候,会调用[self subscribe:o],这里实际是调用了RACDynamicSignal类里面的subscribe方法。

    - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
        NSCParameterAssert(subscriber != nil);
        RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
        subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
        if (self.didSubscribe != NULL) {
            RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
                RACDisposable *innerDisposable = self.didSubscribe(subscriber);
                [disposable addDisposable:innerDisposable];
            }];
            [disposable addDisposable:schedulingDisposable];
        }
        return disposable;
    }
    

    上面的代码中我们不难看出:除了对于订阅者和清理对象的再次封装外,最重要的就是创建信号时为信号设置Block(didSubscribe)被调用了,而且Block参数使用了我们创建的订阅者。

    1、RACDisposable有3个子类,其中一个就是RACCompoundDisposable。

    @interface RACCompoundDisposable : RACDisposable
    + (instancetype)compoundDisposable;
    + (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables;
    - (void)addDisposable:(RACDisposable *)disposable;
    - (void)removeDisposable:(RACDisposable *)disposable;
    @end
    

    RACCompoundDisposable虽然是RACDisposable的子类,但是它里面可以加入多个RACDisposable对象,在必要的时候可以一口气都调用dispose方法来销毁信号。当RACCompoundDisposable对象被dispose的时候,也会自动dispose容器内的所有RACDisposable对象。

    2、RACPassthroughSubscriber是一个私有的类。

    
    @interface RACPassthroughSubscriber : NSObject <RACSubscriber>
    @property (nonatomic, strong, readonly) id<RACSubscriber> innerSubscriber;
    @property (nonatomic, unsafe_unretained, readonly) RACSignal *signal;
    @property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;
    - (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable;
    @end
    

    RACPassthroughSubscriber类就只有这一个方法。目的就是为了把信号事件从一个订阅者subscriber传递给另一个还没有disposed的订阅者subscriber。

    - (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
       NSCParameterAssert(subscriber != nil);
     
       self = [super init];
       if (self == nil) return nil;
       _innerSubscriber = subscriber;
       _signal = signal;
       _disposable = disposable;
       [self.innerSubscriber didSubscribeWithDisposable:self.disposable];
       return self;
    }
    

    RACPassthroughSubscriber类中保存了3个非常重要的对象,RACSubscriber,RACSignal,RACCompoundDisposable。RACSubscriber是待转发的信号的订阅者subscriber。RACCompoundDisposable是订阅者的销毁对象,一旦它被disposed了,innerSubscriber就再也接受不到事件流了。

    这里需要注意的是内部还保存了一个RACSignal,并且它的属性是unsafe_unretained。这里和其他两个属性有区别, 其他两个属性都是strong的。这里之所以不是weak,是因为引用RACSignal仅仅只是一个DTrace probes动态跟踪技术的探针。如果设置成weak,会造成没必要的性能损失。所以这里仅仅是unsafe_unretained就够了。

    3、RACScheduler.subscriptionScheduler是一个全局的单例。

    + (instancetype)subscriptionScheduler {
       static dispatch_once_t onceToken;
       static RACScheduler *subscriptionScheduler;
       dispatch_once(&onceToken, ^{
        subscriptionScheduler = [[RACSubscriptionScheduler alloc] init];
       });
       return subscriptionScheduler;
    }
    

    RACScheduler再继续调用schedule方法。

    - (RACDisposable *)schedule:(void (^)(void))block {
       NSCParameterAssert(block != NULL);
       if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];
       block();
       return nil;
    }
    
    + (instancetype)currentScheduler {
     RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
     if (scheduler != nil) return scheduler;
     if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;
     return nil;
    }
    
    + (BOOL)isOnMainThread {
     return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread];
    }
    

    在取currentScheduler的过程中,会判断currentScheduler是否存在,和是否在主线程中。如果都没有,那么就会调用后台backgroundScheduler去执行schedule。
    schedule的入参就是一个block,执行schedule的时候会去执行block。也就是会去执行:

    RACDisposable *innerDisposable = self.didSubscribe(subscriber);
    [disposable addDisposable:innerDisposable];
    

    这两句关键的语句。之前信号里面保存的block就会在此处被“释放”执行。self.didSubscribe(subscriber)这一句就执行了信号保存的didSubscribe闭包。

    在didSubscribe闭包中有sendNext,sendError,sendCompleted,执行这些语句会分别调用RACPassthroughSubscriber里面对应的方法。

    
    - (void)sendNext:(id)value {
     if (self.disposable.disposed) return;
     if (RACSIGNAL_NEXT_ENABLED()) {
      RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description]));
     }
     [self.innerSubscriber sendNext:value];
    }
    - (void)sendError:(NSError *)error {
     if (self.disposable.disposed) return;
     if (RACSIGNAL_ERROR_ENABLED()) {
      RACSIGNAL_ERROR(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString(error.description));
     }
     [self.innerSubscriber sendError:error];
    }
    - (void)sendCompleted {
     if (self.disposable.disposed) return;
     if (RACSIGNAL_COMPLETED_ENABLED()) {
      RACSIGNAL_COMPLETED(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description));
     }
     [self.innerSubscriber sendCompleted];
    }
    

    这个时候的订阅者是RACPassthroughSubscriber。RACPassthroughSubscriber里面的innerSubscriber才是最终的实际订阅者,RACPassthroughSubscriber会把值再继续传递给innerSubscriber。

    - (void)sendNext:(id)value {
     @synchronized (self) {
      void (^nextBlock)(id) = [self.next copy];
      if (nextBlock == nil) return;
      nextBlock(value);
     }
    }
    - (void)sendError:(NSError *)e {
     @synchronized (self) {
      void (^errorBlock)(NSError *) = [self.error copy];
      [self.disposable dispose];
      if (errorBlock == nil) return;
      errorBlock(e);
     }
    }
    - (void)sendCompleted {
     @synchronized (self) {
      void (^completedBlock)(void) = [self.completed copy];
      [self.disposable dispose];
      if (completedBlock == nil) return;
      completedBlock();
     }
    }
    

    innerSubscriber是RACSubscriber,调用sendNext的时候会先把自己的self.next闭包copy一份,再调用,而且整个过程还是线程安全的,用@synchronized保护着。最终订阅者的闭包在这里被调用。

    sendError和sendCompleted也都是同理。

    扩展

    有些信号是有self衍生出来的.如 RACObserve()监听self的一个属性时,在subscribeNext使用self指针,就会形成一个引用环.

    建议使用@weakify@strongify这两个宏来处理指针. 当对象不能使用weak时,使用__unsafe_unretained@unsafeify.

    但很多时候,有一种更好地写法来解决循环指针的问题,如对于一般写法:

    @weakify(self);
    [RACObserve(self, username) subscribeNext:^(NSString *username) {
    @strongify(self);
    [self validateUsername];
    }];
    实际上我们可以这样写:
    
    [self rac_liftSelector:@selector(validateUsername:)     withSignals:RACObserve(self, username), nil];
    或者这样写 :
    
    RACSignal *validated = [RACObserve(self, username) map:^(NSString *username) {
    // Put validation logic here.
    return @YES;
    }];
    

    另外 关于冷热信号,MVVM设计模式+RAC,RACSignal操作的核心bind实现

    文章参考出处:https://www.jianshu.com/p/ba90d649ecb8
    https://blog.csdn.net/weixin_34315665/article/details/85977778
    https://www.cnblogs.com/syios/p/5866668.html
    https://cloud.tencent.com/developer/article/1117009

    相关文章

      网友评论

          本文标题:ReactiveCocoa综合整理

          本文链接:https://www.haomeiwen.com/subject/abstmhtx.html