赞
踩
使用WWHA(Why/What/How/Attention)方法,学习RAC
在学习过程中,别人的博客里面有这句话:
看来,学习一个知识,还是要从WWHA
方向学习
为什么用RAC?
ReactiveCocoa 试图解决以下问题:
比如需要监听登录的两个输入框,控制login按钮是否可点击,就需要写在好几处地方
使用RAC,可以在一个地方写出想要的效果
MVC中,Controller所以逻辑、网络都在Controller中,会造成Controller很大,不以利维护与调试
MVVM中,ViewModel做了Controller的部分工作;
使用RAC,可以将ViewModel与View关联,View 层的变化可以直接响应 ViewModel 层的变化
View 不再与 Model 绑定,也增加了 View 的可重用性
RAC全称为ReactiveCocoa,是由 Github 工程师们开发的一个应用于 iOS 和 OS X 开发的函数响应式编程新框架;ReactiveCocoa 为开发者带来了函数式编程和响应式编程的思想
ReactiveCocoa官网
KVO、代理、通知、Block调用(Block本身不是,Block调用是)都是响应式
Reactive Cocoa是函数式编程(Functional Programing)(FP)思想的实现
那么,什么是函数式编程呢?
你能不能分清?
像计算函数表达式一样来解决一个问题
比如:已知f(x) = 2sin(x + π/2), 求 f(π/2)的值
这个函数可以分为几个小函数:
f1(x) = x + π/2
f2(x) = sin(x)
f3(x) = 2x
那么,原来的函数表达式可以转化为:
f(x) = f3(f2(f1(x)))
也就是,一个复杂的函数,可以分解为N个小的基本函数
或者说N个小的基本函数,可以合成复杂函数
函数的组成三要素:函数名、参数、返回值
对于像上面的函数,每个函数都能接收上一个函数输出的结果(函数为高阶函数),作为自己的输入,这样才能嵌套生成最终结果,同时,计算的顺序也是一定从里向外,所以换个写法可以写成:
start ---x--> f1(x) --(temp value1)--> f2(temp value1) --(temp value2)--> f3(temp value2) ---> result
里面f1(x),f2(x),f3(x)就可以看成流,或者说是RAC中的RACStream对象
Stream就是一个 按时间排序的Events(Ongoing events ordered in time)序列
高阶函数:函数可以做返回值或参数
惰性计算:也叫延迟求值,意味着对象在需要时求值,而不是在创建时求值。(类似懒加载)
通过高阶函数以点为连接将多个函数连接在一起完成参数传递和复杂的操作!
函数式(链式)编程举例:
例如在Masonry
中的这样的代码
make.right.equalTo(self.right).insets(kPadding);
将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好a(1).b(2).c(3)。
链式编程特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)
代表:masonry框架
响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。
举个例子,如果你要求 c = a + b; 那么你必须得知道 a 跟 b 的值,先定义 a 跟 b,并给他们赋值,这是正常的思路。
但是响应式编程的话,你可以先定义 a 跟 b 的值,然后让 c = a + b,在后面给 a 跟 b 赋值,把 a 跟 b 与 c 绑定了,赋值后 c 的值也会相应的改变。
这就是响应式编程思想,经过上面一个小例子,相比大家应该能理解上面那句不需要考虑调用顺序,只需要考虑结果这句话的意思了吧。
代表:KVO运用
RAC主要包括的核心组件:
RACSteam
RACSequence RACSignal
RACSubscriber
RACDisposable
RACScheduler
Cocoa框架适配工具
RACStream是一个抽象类,是不能直接实例化的;
RACStream作为一个描述抽象的父类,方法由具体子类来实现;
RACStream的两个子类分别是RACSignal
和RACSequence
RACSignal
“A signal, represented by the RACSignal class, is a push-driven stream.”
也就是,RACSignal是一个push-driven stream
RACSignal有很多方法,你可以用来去订阅这些不同的事件类型。每一个方法接收一个或多个block,当事件发生时,block中的逻辑会被执行。在这种情况下,您可以看到subscribeNext:方法用于提供在每个next事件上执行的块。
ReactiveCocoa框架使用了categories去给很多标准的 UIKit 组件增加signals,这样你可以给它们增加订阅,这就是这个文本域的rac_textSignal域的来源。
比如
[self.usernameTextField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
rac_textSignal就是一个UITextField的分类
在ReactiveCocoa指导一笔记这篇文章中,作者给我们举了一个监听UITextField的小例子,很是经典。
在下面的代码例子中:
[[[self.usernameTextField.rac_textSignal
map:^id(NSString *text) {
return @(text.length);
}]
filter:^BOOL(NSNumber *length) {
return [length integerValue] > 3;
}]
subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
map接收了NSString输入,并且获取了它的长度,用一个NSNumber返回出去。
其工作原理的图像:
这个RAC宏允许你分配一个signal的输出给一个对象的属性。
它使用了两个参数,第一个是那个对象,包含着将要设置的属性,第二个是属性名。
每一次signal发射一个next事件,被传递的值就被分配给给定的属性。
第一个参数self.usernameTextField与第二个参数backgroundColor绑定
将block内部的return结果,赋值给第二个参数
这里你可以看到两个简单的管道,接收文本signal,把他们map成指示有效的布尔型,然后再map成一个 UIColor,和文本域的background的color属性绑定起来
当前的代码已经有了signal发射了一个布尔值去指示着username和password域是否有效:validUsernameSignal
和validPasswordSignal
。
你的任务是合并这两个signal去决定什么时候可以让这个按钮开始工作。
RACSignal *signupActiveSignal = [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
//@()是一个NSNumber,其内部是bool类型
return @([usernameValid boolValue] && [passwordValid boolValue]);
}];
[signupActiveSignal subscribeNext:^(NSNumber *signupActive) {
self.signInButton.enabled = [signupActive boolValue];
}];
上面的代码使用了combineLatest:reduce:
方法去合并由validUsernameSignal和validPasswordSignal发射的最后的值到一个新的signal。
每一次这两个源signal中的一个发射出一个新值,这个reduce block就执行,并且它返回的值被作为combined signal的next的值。
上面描绘了两个重要的概念:
上面的方法创建了一个signal,发射于当前的用户名和密码。
上面的代码使用了RACSignal的createSignal:
,为了来创建signal。
描述了这个signal的block是一个参数,传递给这个方法。当这个signal有一个订阅者,这个block中的代码将会被执行。
这个block被传递了一个subscriber实例,实现了RACSubscriber协议,这个协议拥有一些方法,使得你可以调用去发射event;你也可以发射任意数量的next事件,最后终止于一个error或者complete事件。在这个案例中,它发送了一个next事件,指明了sign-in是否成功,跟随者一个complete事件。
可以这么理解,使用
[RACSignal createSignal:]
方法,block内部可以都写上:
[subscriber sendNext:xxx];
[subscriber sendCompleted];
return nil;
这个block的返回类型是一个RACDisposable对象,它允许你去执行一些清理工作 – 当一个subscription(订阅)被取消或者被丢弃时需要进行的。
这里的这个signal没有清理的需求,所以返回了一个nil。
RACSignal 是基于时间的数据流
RACSequence是不基于时间的数据流
怎么理解push-driver和pull-driver呢?
push-driver是任何时刻有数据了都会push给调用者,如果你没处理就丢失了。
pull-driver是任何时刻我有数据了你都可以获取到,因为数据先存储了,取数据的时间控制在调用者上。
更多参考学习: iOS 开源库源码分析之 ReactiveCocoa
Push-driver:被动获取,类比看电视,你不看就没了
Pull-driver:主动获取,类比读书,你想读多少读多少
或
Push-driver可以类比看电视,节目不管你看不看,都一直播放,你错过了就是错过了。
Pull-driver可以类比看书,知识和文字不管你看不看,一直都在书里。
RACSignal -> Push-driver
RACSequence -> Pull-driver
RACSignal有休眠(cold)和激活(hot)两种状态,也就是所谓的冷信号和热信号;
一般情况下,一个RACSignal创建之后都处于cold状态,有人去subscribe才被激活。
RACSignal能产生且只能产生三种事件:next、completed,error
一个Subject,在RAC中代表的是RACSubject类,是一种可以被手动控制的信号。Subject可以认为是可变的信号,就像NSMutableArray对于NSArray一样。Subject是很有用的连接非RAC代码到RAC的很有用的工具。
sequence,在RAC中代表的是RACSequence类,是一种pull-driven的流。Sequence是一种集合类型,类似NSArray.
RACSubscriber是订阅者,所有实现了 RACSubscriber 协议的类都可以作为信号源的订阅者。一次订阅是通过调用-subscribeNext:error:completed产生的。订阅会持有它的signal对象,并且在信号completed或者error的时候释放
调度器,是一个串行的信号执行队列,用来执行任务或者传递结果。Schedulers类似于GCD中的队列,但是scheduler支持取消队列(通过disposables),并总是串行执行的
清洁工,Disposables常常用来取消对一个信号的订阅。
command,在RAC中表示的是RACCommand类,可以创建或者订阅一个信号用来响应某些action动作。这可以很方便的来处理App中的用户交互。
在项目中,使用Cocoapods管理,直接pod 'ReactiveObjC', '~>3.1.1'
即可
值可以分为三种:普通值、元组、信号
万物皆信号
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。