写这篇文章的目的,在于记录自己的学习内容,理顺思路。
RAC(Reactive Cocoa)
Cocoa是苹果整套框架的简称。Reactive响应。
第一次听说RAC,是有人问我,你知道函数响应式编程吗?
RAC就是。
那么,什么是函数响应式编程?
RAC让人觉得厉害的大概就是它的编程思想,事件的处理,经常会有一种让人忍不住赞叹一声,啊!或是一语惊醒梦中人的顿悟感。哈哈,我这个小白,在学习的过程中,是这样子没错啦。
函数式编程:
相关操作使用函数或者方法调用。
特点:每个方法都有返回值,并且使用block或函数作为参数。
顾名思义,采用函数的思想。
函数式编程,多采用闭包与高阶函数,函数是“第一等公民”,惰性计算(只用“表达式”,不用“语句”),递归(无“副作用”),不修改状态(引用透明性)。
例如,数学中常见的函数表达式
y = f(x) ——> y = f(f(x)) ,该表达式的参数是一个函数
应用到OC中,形式就大概如下
- (void)test:(void(^)(NSString *))block{
block = ^(NSString *name){
//表达式
};
}
响应式编程:
这种于操作结果,而不在意该结果究竟是按照怎样的操作顺序发生变化导致的。有点类似于蝴蝶效应,a发生变化,b自然跟随发生变化。
例如:
int a = 10;
int b = a+10;
我们希望a的值改变时,b的值随之改变,这就是响应,b自然响应a
具体,推荐阅读袁峥大神的https://www.jianshu.com/p/87ef6720a096
几种常见的编程思想介绍具体,而且还有使用例子。
RACSignal
RAC的使用过程,不外乎于
1.信号的产生 2.订阅信号 3.发送信号 4.信号销毁。
//1:信号的产生
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
// block调用时刻:每当有订阅者订阅信号,就会调用block。
//3:发送信号
[subscriber sendNext:@"龙晨"];
// 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
NSError *error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:1008611 userInfo:@{@"key":@"我的错"}];
[subscriber sendError:error];
//4:信号销毁
//RACDisposable 信号回收站
return [RACDisposable disposableWithBlock:^{
// block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不在被订阅了。
NSLog(@"信号销毁了");
}];
}];
//2:订阅信号
[signal subscribeNext:^(id _Nullable x) {
// block调用时刻:每当有信号发出数据,就会调用block.
NSLog(@"%@",x);
}];
[signal subscribeError:^(NSError * _Nullable error) {
NSLog(@"%@",error);
}];
代码自上而下调用,那么3和4的发生,为什么会在2之后呢?明明3和4的代码在前呀?
为什么呢?
RACSignal,信号类,默认是冷信号,只有订阅了这个信号,才会变为热信号,值发生变化时触发。
订阅信号,调用RACSignal的subscribeNext方法。
1.创建信号,调用的方法
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
我们注意到,该方法的参数为didSubscribe的一个block,返回值是一个RACDynamicSignal类。
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
在该方法中,didSubscribe被保存起来,此时并不会触发。
2.当信号被订阅时,在创建RACSubscriber的同时,nextBlock也被保存起来
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
- (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;
}
+ (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;
}
- (instancetype)init {
self = [super init];
@unsafeify(self);
RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{
@strongify(self);
@synchronized (self) {
self.next = nil;
self.error = nil;
self.completed = nil;
}
}];
_disposable = [RACCompoundDisposable compoundDisposable];
[_disposable addDisposable:selfDisposable];
return self;
}
同时还创建了RACCompoundDisposable与RACPassthroughSubscriber(真正的订阅者类),同时RACDynamicSignal的didSubscribe被调用。
而在didSubscribe中,会调用[subscriber sendNext:];
#pragma mark RACSubscriber
- (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)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
sendNext底层其实就是执行subscriber的nextBlock
RACSignal可用于textFiled,button,手势,通知,KVO,定时器等。
@weakify(self);
//textField
[self.accountTF.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
@strongify(self);
self.imgV.image = [UIImage imageNamed:x];
if (self.imgV.image == nil) {
self.imgV.image = [UIImage imageNamed:@"touxiang"];
}
}];
//button
[[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
@strongify(self);
}];
//定时器
[[RACSignal interval:1 onScheduler:[RACScheduler scheduler]] subscribeNext:^(NSDate * _Nullable x) {
NSLog(@"%@",x);
}];
//手势
UITapGestureRecognizer *tap = [UITapGestureRecognizer new];
self.stateLabel.userInteractionEnabled = YES;
[self.stateLabel addGestureRecognizer:tap];
[[tap rac_gestureSignal] subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
NSLog(@"%@",x);
}];
//通知
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidChangeFrameNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
NSLog(@"%@",x);
}];
//KVO
[RACObserve(self, name) subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
RAC中常见的相关操作:
1.信号映射:map与flattenMap
2.信号过滤:filter、ignore、 distinctUntilChanged
3.信号合并: combineLatest、reduce、merge、zipWith
4.信号连接:concat、then
5.信号操作时间:timeout、interval、dely
6.信号取值:take、takeLast、takeUntil、
7.信号跳过:skip
8.信号发送顺序:donext、cocompleted
9.获取信号中的信号:switchToLatest
10.信号错误重试:retry
简单使用举例:
//map flattenMap
//实际应用时 通常会跳过第一次
[[[self.accountTF.rac_textSignal skip:1] flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
return [RACSignal return:[NSString stringWithFormat:@"+86-%@",value]];
}] subscribeNext:^(id _Nullable x) {
NSLog(@"flattenMap === %@",x);
}];
//filter
[[[self.passwordTF.rac_textSignal skip:1] filter:^BOOL(NSString * _Nullable value) {
//做过滤条件
@strongify(self);
if (self.passwordTF.text.length > 5) {
self.passwordTF.text = [self.passwordTF.text substringToIndex:5];
self.stateLabel.text = @"密码长度不能大于5";
}
return value.length < 5;
}] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"我订阅到了什么:%@",x);
//逻辑区域
}] ;
//组合 combineLaster
RACSignal *signalA = self.accountTF.rac_textSignal;
RACSignal *signalB = self.passwordTF.rac_textSignal;
[[RACSignal combineLatest:@[signalA, signalB] reduce:^id (NSString *account,NSString *password){
//reduceBlock的参数个数 要与 合并的信号数组的个数保持一致
//此处 可以将多个参数合并
return @(account.length && password.length);
}] subscribeNext:^(NSNumber *x) {
self.loginBtn.backgroundColor = x.integerValue ? [UIColor greenColor] : [UIColor darkGrayColor];
self.loginBtn.enabled = x.integerValue;
}];
//takeUntil
//subject1 正常发送,直到subject2发送信号。意味着subject2发送信号后 subject1不能再发送信号
//以下代码,reboot不能正常发送,除非注释掉 [subject2 sendNext:@"hani"];
RACSubject *subject1 = [RACSubject subject];
RACSubject *subject2 = [RACSubject subject];
[[subject1 takeUntil:subject2] subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[subject1 sendNext:@"菲尼克斯"];
[subject1 sendNext:@"艾拇"];
[subject2 sendNext:@"hani"];
[subject1 sendNext:@"reboot"];
说明:
(a)flattenMap:在bind基础上封装的改变 法, 提 供的block,改变当前流,变成block返回的流对象。
(b)flatten:在flattenMap基础封装的改变 法,如果当前 反应流中的对象也是 个流的话,就可以将当前流变成当前
流中的流对象
(c)map:在flattenMap基础上封装的改变 法,在 flattenMap中的block中返回的值必须也是流对象, map则
需要,它是将流中的对象执 block后, 流的return 法 将值变成流对象。
(d)mapReplace:在map的基础上封装的改变 法,直接 替换当前流中的对象,形成 个新的对象流。
(e)filter:在Map基础上封装的改变封装,过滤掉当前流 中 符合要求的对象,将之变为空流
(f)ignore:在filter基础封装的改变 法,忽 和当 前值 样的对象,将之变为空流
(g)skip:在bind基础上封装的改变 法,忽 当前 流前n次的对象值,将之变为空流
(h)take:在bind基础上封装的改变 法,只区当 前流中的前n次对象值,之后将流变为空( 是空
流)。
(i)distinctUntilChanged:在bind基础封装的改变 法,当流中后 次的值和前 次的值 同的时候,
才会返回当前值的流,否则返回空流(第 次默认被 忽 )
(j)takeUntilBlock:在bind基础封装的改变 法,取当前 流的对象值,直到当前值满 提供的block,就会将当前流
变为空( 是空流)
(k)takeWhileBlock:在bind基础封装的改变 法,取当
前流的对象值,直到当前值 满 提供的block,就会将当 前流变为空( 是空流)
(l)skipUntilBlock:在bind基础封装的改变 法,忽 当 前流的对象值(变为空流),直到当前值满 提供的
block。
(m)skipWhileBlock:在bind基础封装的改变 法,忽 当前流的对象值(变为空流),直到当前值 满 提供的 block
(n)scanWithStart: reduceWithIndex:在bind基
础封装的改变 法, 同样的block执 每次流中的
值,并将结果 于后 次执 当中,每次都把block 执 后的值变成新的流中的对象。
(o)startWIth:在contact基础上封装的多流之间 的顺序 法,在当前流的值流出之前,加 个初
始值 (p)zip:打包多流,将多个流中的值包装成 个
RACTuple对象
(q)reduceEach:将流中的RACTuple对象进 过 滤,返回特定的衍 出的 个值对象
网友评论