*****阅读完此文,大概需要30分钟*****
一、FRP的概念
RAC(ReactiveCocoa)是由GitHub团队开发的基于Cocoa的FRP框架。提起FRP,即Functional Reactive Programming(函数式响应式编程),在很多开发领域都有广泛的应用,例如android或者后端开发中有RxJava,尤其是在前端的领域中(react、ajax、vue等框架)应用更为广泛。在iOS移动端目前应用最为经典的就是RAC。
接触RAC之后,我的感觉就是,RAC算得上是业务开发的利器,它的应用可以极大地提高编程开发效率, 它目前在美团、点评客户端开发中,应用比较广泛,已经有很多经典的组件化方案都是基于RAC的。虽说它的学习成本很高(据说一个2~3年经验的iOS开发者,想要精通RAC,也需要2~3月时间,感觉有点夸张),但是把RAC常用的一些操作掌握并应用到业务开发中,还是不难的。下面是RAC github官方地址:
https://github.com/ReactiveCocoa/ReactiveCocoa
二、信号的概念
介绍信号之前,不得不扯点函数响应式编程的概念,如下面:
y = a+b+c;
一个或者多个输入,对应到唯一的y输出,这样便构成一个高阶函数。如果a、b、c中一个或者多个变化(输入变化),y也要跟着变化。通常我们会等a、b、c变化结束后,再一起计算最终的结果y,a、b、c变化结束后并不会及时带来y的变化;如果我们将a、b、c的变化和y提前做好映射(绑定),a、b、c任何一个变化y都能同时映射到对应的变化,便是函数响应式编程。在此基础之上,我们将a、b、c的变化进行抽象封装,为什么要抽象封装呢?因为只有这样,我们才好统一进行管理,而这个抽象封装出来的东西,便构成了信号(Signal)。举个简单的栗子:我们构造了三个输入框,产生a、b、c三个值,正常情况下,当a变化时,y会随后发生变化,如图:
测试代码如下:
@property (nonatomic, strong) UITextField *myTextField1;
@property (nonatomic, strong) UITextField *myTextField2;
@property (nonatomic, strong) UITextField *myTextField3;
@property (nonatomic, strong) NSString *myTextFieldString1;
@property (nonatomic, strong) NSString *myTextFieldString2;
@property (nonatomic, strong) NSString *myTextFieldString3;
@property (nonatomic, strong) UILabel *resultLabel;
当a或b或c发生变化时,会进行下面的回调:
- (void)onTextFieldChanged:(UITextField *)textField forEvent:(UIEvent *)event
{
if (textField.tag == 1000) {
NSLog(@"Result------->%@",self.resultLabel.text);
self.myTextFieldString1 = textField.text;
NSLog(@"Result------->%@",self.resultLabel.text);
}
if (textField.tag == 1001) {
self.myTextFieldString2 = textField.text;
}
if (textField.tag == 1002) {
self.myTextFieldString3 = textField.text;
}
[self onValueChanged];
NSLog(@"Result------->%@",self.resultLabel.text);
}
- (void)onValueChanged
{
NSInteger result = [self.myTextFieldString1 integerValue] + [self.myTextFieldString2 integerValue] + [self.myTextFieldString3 integerValue];
self.resultLabel.text = [NSString stringWithFormat:@"结果:%@",@(result)];
[self.resultLabel sizeToFit];
}
当1改为10后,打印的结果如下:
2017-12-03 19:44:10.687291+0800 MDProject[43978:1616323] Result------->结果:6
2017-12-03 19:44:10.687471+0800 MDProject[43978:1616323] Result------->结果:6
2017-12-03 19:44:10.688152+0800 MDProject[43978:1616323] Result------->结果:15
响应式编程的示意图,如下:
实现代码如下:
@weakify(self);
RACSignal *signal1 = [RACObserve(self, myTextFieldString1) distinctUntilChanged];
RACSignal *signal2 = [RACObserve(self, myTextFieldString2) distinctUntilChanged];
RACSignal *signal3 = [RACObserve(self, myTextFieldString3) distinctUntilChanged];
RACSignal *resultSignal = [RACSignal combineLatest:@[signal1, signal2, signal3] reduce:^id(NSString *s1, NSString *s2,NSString *s3){
return [NSString stringWithFormat:@"%@",@([s1 integerValue] + [s2 integerValue] +[s3 integerValue])];
}];
[resultSignal subscribeNext:^(id x) {
@strongify(self);
self.resultLabel.text = [NSString stringWithFormat:@"结果:%@",x];
[self.resultLabel sizeToFit];
}];
- (void)onTextFieldChanged:(UITextField *)textField forEvent:(UIEvent *)event
{
if (textField.tag == 1000) {
NSLog(@"Result------->%@",self.resultLabel.text);
self.myTextFieldString1 = textField.text;
NSLog(@"Result------->%@",self.resultLabel.text);
}
if (textField.tag == 1001) {
self.myTextFieldString2 = textField.text;
}
if (textField.tag == 1002) {
self.myTextFieldString3 = textField.text;
}
}
我们在此分别为三个输入框的值构造了三个signal,任何一个信号的“变化”,都会带来result的变化。执行的结果如下:
2017-12-03 19:50:11.684640+0800 MDProject[44355:1633745] Result------->结果:6
2017-12-03 19:50:11.685474+0800 MDProject[44355:1633745] Result------->结果:15
我们看到, self.myTextFieldString1 = textField.text;(a发生变化)之后,结果就立即发生了变化。
三、RAC信号的实现原理
前面我已经由浅入深地介绍了signal这个概念,我们知道,signal是用来传递的,既然有了传递的概念,那么就会有信号的发送者(信号的create),和接受者(信号的订阅)。前面的例子中我们知道,a、b、c的变化产生信号,订阅者拿到变化,刷新了UI;下面演示一下,一个信号,从产生到结束的详细过程,代码如下:
- (void)coldSignalTest
{
//信号的创建
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
[subscriber sendNext:@1];//信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:10 schedule:^{
[subscriber sendNext:@2];//信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{
[subscriber sendNext:@3];//信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{
[subscriber sendCompleted];//信号的取消订阅
}];
return nil;
}];
//信号的订阅者1
[signal subscribeNext:^(id x) {
NSLog(@"Subscriber 1 recveive: %@", x);
}];
//信号的订阅者2
[[RACScheduler mainThreadScheduler] afterDelay:2.0 schedule:^{
[signal subscribeNext:^(id x) {
NSLog(@"Subscriber 2 recveive: %@", x);
}];
}];
}
从上面的代码注释中,可以看出信号的生命周期,包括分为信号的创建、信号的订阅、信号的发送、信号的订阅的取消。
1、信号的创建
+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
这里createSignal:创建了一个RACDynamicSignal的信号,并将发送信号部分的block(didSubscribe)传了进去,我们再往下看:
+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
在这里我们可以看到,有个copy操作,将didSubscribe这个临时栈block放到了内存中保存。
2、信号的订阅
我们看下订阅信号的源码:
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
这里将订阅者block封装成了一个RACSubscriber,我们看下这步封装,如下:
+ (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;
}
订阅者block被复制给RACSubscriber的_next,这个_next后面还有介绍。
我们再执行subscribe操作,进行订阅操作,由于RACDynamicSignal是RACSignal的子类,所以,这部操作的代码在RACDynamicSignal中,如下:
- (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;
}
这段代码比较抽象,但是我们可以看出,中间被订阅者self.didSubscribe被执行了,这就是(1)中保存的_didSubscribe。
3、信号的发送
信号的发送源码,如下:
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
信号的发送就是执行了相应的block(value),而这个self.next正是前面保存的订阅者的_next部分,由此我们可以看出,信号的传递本身是一个抽象概念,订阅者和被订阅者之间的绑定、以及信号的发送,都是通过执行共同的block(_didSubscribe和_next)来完成的。
4、信号的取消订阅
我们前面(2)可以看到信号的订阅时,订阅者block被封装到相应的RACDisposable中,当我们执行[subscriber sendCompleted]操作时,其中也就执行以下代码:
- (void)dispose {
.......
#if RACCompoundDisposableInlineCount
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
inlineCopy[i] = _inlineDisposables[i];
_inlineDisposables[i] = nil;
}
#endif
......
}
在前面信号的订阅时,订阅者block被封装成disposable,然后添加到disposable数组,而订阅者的取消,也是数组被置为nil的操作,然后被系统dealloc的过程。
四、冷信号与热信号
1、冷热信号的引入
在讲解这两个概念之前,我要先通过一个简单粗暴的例子,来便于这两个概念的理解。
2017年7月的某一天,初中生小明为了提升作文水平,选择订阅《读者》,这时他有两个选择,商家A提供的选择是,如果订了他家的《读者》,可以从第二月开始(2017年8月)每月都收到一份最新的《读者》,这样他全年可以收到5份《读者》。商家B提供的选择是,他会从2017年8月开始每月依次发给你2017年1月、2月、3月...4月的报纸,也就是说8月开始小明其实收到的是1月份的旧《读者》,不过这样他最终可以把全年的12份《读者》全部都收到。
好了,有个这个概念的铺垫,我们下面直接看示例代码:
- (void)coldSignalTest
{
//信号的创建
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
[subscriber sendNext:@"一月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{
[subscriber sendNext:@"二月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{
[subscriber sendNext:@"三月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{
[subscriber sendNext:@"四月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{
[subscriber sendNext:@"五月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:6 schedule:^{
[subscriber sendNext:@"六月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:7 schedule:^{
[subscriber sendNext:@"七月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:8 schedule:^{
[subscriber sendNext:@"八月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:9 schedule:^{
[subscriber sendNext:@"九月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:10 schedule:^{
[subscriber sendNext:@"十月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:11 schedule:^{
[subscriber sendNext:@"十一月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:12 schedule:^{
[subscriber sendNext:@"十二月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:13 schedule:^{
[subscriber sendCompleted]; //信号的取消订阅
}];
return nil;
}];
//信号的订阅者
[[RACScheduler mainThreadScheduler] afterDelay:7.0 schedule:^{
[signal subscribeNext:^(id x) {
NSLog(@"小明 recveive: %@", x);
}];
}];
}
在此我们通过延迟时间来模仿,小明收到的每月的读者,打印结果如下:
2017-12-0419:12:35.517553+0800 MDProject[81121:3596317] Signal was created.
2017-12-0419:12:43.528421+0800 MDProject[81121:3596317] 小明 recveive: 一月份的《读者》
2017-12-0419:12:44.525774+0800 MDProject[81121:3596317] 小明 recveive: 二月份的《读者》
2017-12-0419:12:45.528010+0800 MDProject[81121:3596317] 小明 recveive: 三月份的《读者》
2017-12-0419:12:46.527258+0800 MDProject[81121:3596317] 小明 recveive: 四月份的《读者》
2017-12-0419:12:47.525560+0800 MDProject[81121:3596317] 小明 recveive: 五月份的《读者》
2017-12-0419:12:48.525488+0800 MDProject[81121:3596317] 小明 recveive: 六月份的《读者》
2017-12-0419:12:49.527300+0800 MDProject[81121:3596317] 小明 recveive: 七月份的《读者》
2017-12-0419:12:50.528087+0800 MDProject[81121:3596317] 小明 recveive: 八月份的《读者》
2017-12-0419:12:51.528652+0800 MDProject[81121:3596317] 小明 recveive: 九月份的《读者》
2017-12-0419:12:52.528625+0800 MDProject[81121:3596317] 小明 recveive: 十月份的《读者》
2017-12-0419:12:53.526341+0800 MDProject[81121:3596317] 小明 recveive: 十一月份的《读者》
2017-12-0419:12:55.529068+0800 MDProject[81121:3596317] 小明 recveive: 十二月份的《读者》
我们将上面的代码改编后,如下:
- (void)hotSignalTest
{
RACSubject *signal = [RACSubject subject];
[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
[signal sendNext:@"一月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{
[signal sendNext:@"二月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{
[signal sendNext:@"三月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{
[signal sendNext:@"四月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{
[signal sendNext:@"五月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:6 schedule:^{
[signal sendNext:@"六月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:7 schedule:^{
[signal sendNext:@"七月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:8 schedule:^{
[signal sendNext:@"八月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:9 schedule:^{
[signal sendNext:@"九月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:10 schedule:^{
[signal sendNext:@"十月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:11 schedule:^{
[signal sendNext:@"十一月份的《读者》"]; //信号的发送
}];
[[RACScheduler mainThreadScheduler] afterDelay:12 schedule:^{
[signal sendNext:@"十二月份的《读者》"]; //信号的发送
}];
NSLog(@"Signal was created.");
[[RACScheduler mainThreadScheduler] afterDelay:7.0 schedule:^{
[signal subscribeNext:^(id x) {
NSLog(@"小明 recveive: %@", x);
}];
}];
}
打印结果如下:
2017-12-04 19:16:18.491057+0800 MDProject[81340:3606937] Signal was created.
2017-12-04 19:16:26.491986+0800 MDProject[81340:3606937] 小明 recveive: 八月份的《读者》
2017-12-04 19:16:27.490074+0800 MDProject[81340:3606937] 小明 recveive: 九月份的《读者》
2017-12-04 19:16:28.490190+0800 MDProject[81340:3606937] 小明 recveive: 十月份的《读者》
2017-12-04 19:16:29.490196+0800 MDProject[81340:3606937] 小明 recveive: 十一月份的《读者》
2017-12-04 19:16:31.490209+0800 MDProject[81340:3606937] 小明 recveive: 十二月份的《读者》
以上两个例子对比可以看出,商家A提供的是一种“热信号”,而商家B提供的是一种“冷信号”。
2、热信号实现原理
前面的例子中可知,RACSubject相关的操作会产生热信号。我先来看一下热信号的产生过程:
(1)RACSubject的初始化
RACSubject *signal = [RACSubject subject];
这一步的源码如下:
- (id)init {
self = [super init];
if (self == nil) return nil;
_disposable = [RACCompoundDisposable compoundDisposable];
_subscribers= [[NSMutableArray alloc] initWithCapacity:1];
return self;
}
这一步初始化了一个_subscribers数组,看名字就该知道,这是用来存放订阅者的数组。
(2)订阅者初始化
我们单步跟踪进去,看一下:
- (RACDisposable *)subscribe:(id)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
[subscribers addObject:subscriber];
}
return [RACDisposable disposableWithBlock:^{
@synchronized (subscribers) {
// Since newer subscribers are generally shorter-lived, search
// starting from the end of the list.
NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id obj, NSUInteger index, BOOL *stop) {
return obj == subscriber;
}];
if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
}
}];
}
我们先留意下关键代码,可以看到,订阅者代码被加到了,1)中的订阅者数组中。
(3)信号的发送
我们看下RACSubject的SendNext方法:
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id subscriber) {
[subscriber sendNext:value];
}];
}
- (void)enumerateSubscribersUsingBlock:(void (^)(id subscriber))block {
NSArray *subscribers;
@synchronized (self.subscribers) {
subscribers = [self.subscribers copy];
}
for (id subscriber in subscribers) {
block(subscriber);
}
}
以上代码不难看出,sendNext这一步会遍历订阅者数组,依次执行每个订阅者的block方法。
3、冷信号和热信号的区别
有了上面的例子,冷信号与热信号的区别,其实很明显了:
1)冷信号是一对一的,订阅者与被订阅者通过block绑定,没有订阅者,就没有被订阅者,有了订阅者代码执行,被订阅者代码就会被完整地执行,所以冷信号给我们带来的是完整信号。
2)热信号的概念类似于Notification的概念,它不管某一个订阅者代码是否被执行,它都会去遍历订阅者数组,依次执行被订阅者代码,如果你先到,你就可以“先拿到”较早的信号,如果你晚加入订阅者数组,你就只能被晚点遍历到,接收到较晚的信号。
领取专属 10元无门槛券
私享最新 技术干货