这篇文章将会总结RAC在项目中的运用场景,不涉及RAC核心以及原理。
关于响应式编程
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便的表达静态和动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
举个例子:
在网上的很多例子中都会用a=b+c;来举例说明,具体的解释是这样的:在命令式编程环境中,a:=b+c表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。在这里有一个很大的误解,它这里的解释其实是通过值的变化不同而体现出响应式比命令式更加灵活。但这里其实是通过执行者的任务繁琐程度来体现出响应式的优势的。比如这里:b+c、setter b、setter c其实就是一段要执行的逻辑,a 是我们要的结果,在响应式编程环境下,我们只需要setter b、setter c就会实时将数据流传递给b+c从而输出a。在命令编程模式下,b+c、setter a、setter b是独立的执行单位,我们要不断的给执行单位发送命令来得到想要的结果。这里,其实就能简单的看出来响应式的灵活性,如果我们在实际项目中去运用响应式编程思想就可以大大减弱执行的复杂性。
因此,响应式编程思想和命令式编程思想其优势不应该是被体现在结果上,而是体现在执行过程上,也就我们所说的灵活性。
关于iOS系统的响应式编程
KVO
KVO:(key-value-observer)键值观察者,是观察者设计模式的一种体现。
KVO触发机制:一个对象(观察者),检测另一个对象(被观察者)的属性是否发生变化,若被观察者的属性发生了更改,会触发观察者的一个方法(方法名固定,类似代理方法)。
使用步骤
- 注册观察者(为被观察这指定观察者以及被观察者属性)
- 实现回调方法
- 触发回调方法
- 移除观察者
举例:
/////Person.h
@interface Person : NSObject
@property(nonatomic,strong)NSString *name;//姓名
//第一种就是直接赋值
- (void)changeName:(NSString*)name;
//第二种点语法赋值
- (void)changeNameFromSetter:(NSString*)name;
@end
/////Person.m
#import "Person.h"
@implementation Person
//第一种就是直接赋值
- (void)changeName:(NSString*)name{
_name = name;
}
//第二种点语法赋值
- (void)changeNameFromSetter:(NSString*)name {
self.name = name;
}
@end
注册观察者:
//observer观察者 (观察self.view对象的属性的变化)
//KeyPath: 被观察属性的名称
//options: 观察属性的新值,旧值等的一些配置(枚举值)
//context:上下文 可以为kvo的回调方法传值
//这儿的self.view是被观察者
//注册观察者(可以是多个)
/*
options: 有4个值,分别是:
NSKeyValueObservingOptionOld 把更改之前的值提供给处理方法
NSKeyValueObservingOptionNew 把更改之后的值提供给处理方法
NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值。
NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后。
*/
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld context:nil]
;
实现回调方法:
#pragma mark - kvo的回调方法(系统提供的回调方法)
//keyPath:属性名称
//object:被观察的对象
//change:变化前后的值都存储在change字典中
//context:注册观察者的时候,context传递过来的值
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
id oldName = [change objectForKey:NSKeyValueChangeOldKey];
NSLog(@"oldName----------%@",oldName);
id newName = [change objectForKey:NSKeyValueChangeNewKey];
NSLog(@"newName-----------%@",newName);
//当界面要消失的时候,移除kvo
// [object removeObserver:self forKeyPath:@"name"];
}
触发回调:
[self.person changeName:@"张三"];
移除观察者:
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"name"];
self.person = nil;
}
NSNotification
通知:NSNotification,是iOS开发中一种重要的设计模式,它的实质是程序内部提供的一种广播机制。把接受到的消息根据内部消息转发表,将消息转发给需要的对象。
这个类是通知类,由这个类创建的对象是一个通知对象,也可以理解为是一个消息对象。类中有三个成员变量:
使用步骤
name:是消息对象的唯一标识,接受通知消息时用来辨别
@property (readonly,copy)NSNotificationName name;
object:一个对象,可以理解为针对某个对象的消息
@property (nullable,readonly,retain)id object;
userInfo:一个字典,用来传值
@property (nullable,readonly,copy)NSDictionary *userInfo;
对象方法初始化一个通知对象,并给通知对象的属性赋值
- (instancetype)initWithName:(NSNotificationName)name object:(nullableid)object userInfo:(nullableNSDictionary *)userInfoNS_AVAILABLE(10_6, 4_0) NS_DESIGNATED_INITIALIZER;
类方法创建一个通知对象,这个方法没有userInfo这个属性的初始赋值,所以用来发送无传值的通知
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullableid)anObject;
类方法初始化一个通知对象,并给通知对象的属性赋值
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullableid)anObject userInfo:(nullableNSDictionary *)aUserInfo;
NSNotificationCenter
这个类是通知中心类,内部实现是单利模式,每个程序都有一个默认的通知中心,用来调度通知的发送和接受。
通知中心类相关方法:
a、接收通知的方法:
添加观察者,可以指定一个方法、名称和对象,接受到通知时执行这个指定的方法。这里的name就是通知类的name,只有对应才能接受到通知。
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullableNSNotificationName)aName object:(nullableid)anObject;
发送通知的方法:
发送通知,参数是一个通知对象
- (void)postNotification:(NSNotification *)notification;
发送通知,参数是通知的名称,指定的对象
- (void)postNotificationName:(NSNotificationName)aName object:(nullableid)anObject;
发送通知,参数是通知的名称,指定的对象和传递的参数
- (void)postNotificationName:(NSNotificationName)aName object:(nullableid)anObject userInfo:(nullableNSDictionary *)aUserInfo;
上面这三个方法虽然写法不同,但是功能一样,使用哪一个方法取决于NSNotification类如何创建对象。后两种方法其实就是初始化通知并发送通知,将通知对象的初始化和发送方法结合。
移除通知的方法:
移除该检测对象(observer)下的所有通知
- (void)removeObserver:(id)observer;
根据通知名称(aName),移除该检测对象(observer)下的一个通知
- (void)removeObserver:(id)observer name:(nullableNSNotificationName)aName object:(nullableid)anObject;
函数响应式编程RAC的学习
下面就是这篇文章的重头戏--RAC。
ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。
ReactiveCocoa作用
在我们iOS开发过程中,经常会响应某些事件来处理某些业务逻辑,例如按钮的点击,上下拉刷新,网络请求,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。但是这些事件都用不同的方式来处理,比如action、delegate、KVO、callback等。
这些事件,都可以通过RAC处理,ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。说到这里大家是不是想到了一个词-组件化,没错,RAC确实为组件化提供了很好的帮助。
常见类
RACSiganl信号类
通过RACSignal创建一个信号!! (默认是: 冷信号!!)
通过订阅者!订阅这个信号!(变成: 热信号!!)
发送信号!!
以上就是信号三部曲
- RACEmptySignal :空信号,用来实现 RACSignal 的 +empty 方法;
- RACReturnSignal :一元信号,用来实现 RACSignal 的 +return: 方法;
- RACDynamicSignal :动态信号,使用一个 block - 来实现订阅行为,我们在使用 RACSignal 的 +createSignal: 方法时创建的就是该类的实例;
- RACErrorSignal :错误信号,用来实现 RACSignal 的 +error: 方法;
- RACChannelTerminal :通道终端,代表 RACChannel 的一个终端,用来实现双向绑定。
RACSubscriber :订阅者
RACDisposable :用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
- RACSerialDisposable :作为 disposable 的容器使用,可以包含一个 disposable 对象,并且允许将这个 disposable 对象通过原子操作交换出来;
- RACKVOTrampoline :代表一次 KVO 观察,并且可以用来停止观察;
- RACCompoundDisposable :它可以包含多个 disposable 对象,并且支持手动添加和移除 disposable 对象
- RACScopedDisposable :当它被 dealloc 的时候调用本身的 -dispose 方法。
RACSubject :信号提供者,自己可以充当信号,又能发送信号。
- RACGroupedSignal :分组信号,用来实现 RACSignal 的分组功能;
- RACBehaviorSubject :重演最后值的信号,当被订阅时,会向订阅者发送它最后接收到的值;
- RACReplaySubject :重演信号,保存发送过的值,当被订阅时,会向订阅者重新发送这些值。
RACTuple :元组类,类似NSArray,用来包装值.
RACSequence: RAC中的集合类
RACCommand: RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。
RACMulticastConnection :用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。
RACScheduler: RAC中的队列,用GCD封装的。
- RACImmediateScheduler :立即执行调度的任务,这是唯一一个支持同步执行的调度器;
- RACQueueScheduler :一个抽象的队列调度器,在一个 GCD 串行列队中异步调度所有任务;
- RACTargetQueueScheduler :继承自 RACQueueScheduler ,在一个以一个任意的 GCD 队列为 target 的串行队列中异步调度所有任务;
- RACSubscriptionScheduler :一个只用来调度订阅的调度器。
RAC在实际项目里的简单使用
代替代理实现
以前在使用的代理的时候相当的麻烦,需要定义协议,指定相应的协议,创建遵循协议的对象,然后还需要遵循协议的对象遵循协议,实现协议方法,在这里只需要订阅某一个方法即可。
//实现代理
[[self.hkView rac_signalForSelector:@selector(messageSend:)]subscribeNext:^(RACTuple * x) {
NSLog(@"收到点击事件");
NSLog(@"%@",[x.first objectForKey:@"message"]);
}];
添加按钮事件
之前 我们需要添加Target对象以及方法,才会在点击时响应响应的方法,现在,可以直接实现
//按钮事件的监听
[[self.hkView.btn rac_signalForControlEvents:UIControlEventTouchDown] subscribeNext:^(UIButton * x) {
NSLog(@"UIButton*****%@",x);
}];
接收通知
通知也是项目中偶尔用到的,首先是需要建立通知消息的观察者,然后再有通知中心发送消息,但是,通知中心的方法,需要单独写的,现在也可以在添加通知接收对象的地方直接回调。
//接收通知
//takeUntil会接收一个signal,当signal触发后会把之前的信号释放掉
[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidShowNotification object:nil] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];
注意:这里不建议使用RAC来监听通知,因为这里一般情况下,我们是没必要来管理观察者的释放问题,但这里有时会出现注册者未被释放出现bug
解决方案:
方案一:
//这里这样写只是为了给大家开拓一种思路,selector的方法可以应需求更改,即当这个方法执行后,产生一个信号告知控制器释放掉这个订阅的信号
RACSignal * deallocSignal = [self rac_signalForSelector:@selector(viewWillDisappear:)];
[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"haha" object:nil] takeUntil:deallocSignal] subscribeNext:^(id x) {
NSLog(@"haha");
}];
方案二:
[[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"noti" object:nil] map:^id(NSNotification *value) {
return value.object;
}] distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
方案三:
[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"noti" object:nil] throttle:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
textField文本输入
监听文本的输入,一般通过添加Target监听文本的输入,现在RAC通过Block的方式回调文本输入框内容的改变。
//textField的文本输入监听
[[self.hkView.textField rac_textSignal] subscribeNext:^(NSString * x) {
NSLog(@"文本输入变化***%@",x);
}];
Tap事件的添加
//tap添加点击事件
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc]init];
[[tap rac_gestureSignal] subscribeNext:^(id x) {
NSLog(@"tap");
}];
[self.hkView.label addGestureRecognizer:tap];
KVO
对于KVO的改变,也是特别明显的,可以说是代码量简化了特别多,之前是需要添加观察者,观察某一个属性的新的变化,然后实现,NSObject的扩展方法,这样确实麻烦了不少,RAC可以采用宏定义的方法,订阅信号。
//KVO
[RACObserve(self.hkView.label, text) subscribeNext:^(id x) {
NSLog(@"KVO***%@",x);
}];
定时器
之前使用定时器,都是用的NSTimer,最多也就用点高级一点的GCD的定时器,但是,这些都是在主线程执行的,而且,会收到UI模式的影响,就是常见的滑动的时候,定时器不再执行了,RAC定时器是多线程,是在多线程中执行,RunLoop模式也是采用占位模式,不受UI模式的影响。
//定时器
[[RACSignal interval:1.0 onScheduler:[RACScheduler scheduler]]subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
赋值
RAC(self.hkView.label,text)= self.hkView.textField.rac_textSignal;
Comments | NOTHING