ReactiveCocoa(其简称为 RAC)是Github开源的一个应用于 iOS 和 OS X 开发的框架。RAC 具有函数式编程和响应式编程的特性。
我们都使用Cocoapods集成RAC,需要注意的是Podfile文件中必须使用user_framework!
扩展1.
函数式编程主要就是编程中所有过程可控,尤其在 js 这种没有原则的语言中,过程可控制尤为重要。
函数式编程是由一系列只对参数做转换的纯函数拼接起来构成主体,两头再接输入输出。类似下图:
lc.png
这个程序运行时接受一个参数,就会输出一个结果。
如果用户不断输入新参数,然后运行时(runtime)就在参数发生变化时自动触发当中这个转换逻辑运行,然后运行时再用得到的新结果去修改输出显示。
纯函数:
什么是纯函数呢?纯函数有三个重要的点:
- 函数的结果只受函数参数影响。
- 函数内部不使用能被外部函数影响的变量。
- 函数的结果不影响外部变量。
不可变:
不可变,顾名思义,就是变量或者结构在定义之后不能再发生值的变动,所有操作只是产生新的值而不是去覆盖之前的变量。这样去控制数据,能够让数据流动更加可控。
扩展2.
响应式编程(反应式编程)
响应式编程就是异步数据流编程。咱们常见的单击事件就是一个异步事件流,你可以观察这个流,也可以基于这个流做一些自定义操作。一个流就是一个将要发生的以时间为序的事件序列。它能发射出三种不同的东西:一个数据值(data value)(某种类型的),一个错误(error)或者一个“完成(completed)”的信号。我们只能异步捕捉被发出的事件,使得我们可以在发出一个值事件时执行一个函数,发出错误事件时执行一个函数,发出完成事件时执行另一个函数。有时候你可以忽略后两个事件,只需聚焦于如何定义和设计在发出值事件时要执行的函数。
适合用 RAC的场景:
1、UI 操作,连续的动作与动画部分,例如某些控件跟随滚动。
2、网络库,因为数据是在一定时间后才返回回来,不是立刻就返回的。
3、刷新的业务逻辑,当触发点是多种的时候,业务往往会变得很复杂,用 delegate、notification、observe 混用,难以统一。这时用 RAC 可以保证上层的高度一致性,从而简化逻辑上分层。
关于调试,
1、RAC 源码下有 instruments 的两个插件,方便大家使用。
signalEvents
这个可以看到流动的信号的发出情况,对于时序的问题可以比较好的解决。
diposable
可以检查信号的 disposable 是否正常
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
最终是通过调用自己子类RACDynamicSignal
的createSignal
方法,将这个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
网友评论