ReactiveCocoa综合整理

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  _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 subscriber))didSubscribe {
    return [RACDynamicSignal createSignal:didSubscribe];
}
//RACDynamicSignal.m文件
+ (RACSignal *)createSignal:(RACDisposable * (^)(id 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)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 
@property (nonatomic, strong, readonly) id innerSubscriber;
@property (nonatomic, unsafe_unretained, readonly) RACSignal *signal;
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;
- (instancetype)initWithSubscriber:(id)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable;
@end

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

- (instancetype)initWithSubscriber:(id)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综合整理)