iOS开发笔记之六十八——FRP与RAC介绍(一)

来源:互联网 发布:文明5 mac 语言 编辑:程序博客网 时间:2024/06/08 09:11
******阅读完此文,大概需要60分钟******

一、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;    }}- (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<RACSubscriber> subscriber) {        NSLog(@"被订阅者代码执行");        [[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;    }];    //信号的订阅者    NSLog(@"Signal was created.");    [[RACScheduler mainThreadScheduler] afterDelay:1.0 schedule:^{        NSLog(@"订阅者代码执行");        [signal subscribeNext:^(id x) {            NSLog(@"小明 recveive: %@", x);        }];    }];}
从上面的代码注释中,可以看出信号的生命周期,包括分为信号的创建、信号的订阅、信号的发送、信号的订阅的取消。
1、信号的创建
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {return [RACDynamicSignal createSignal:didSubscribe];}
这里createSignal:创建了一个RACDynamicSignal的信号,并将发送信号部分的block(didSubscribe)传了进去,我们再往下看:
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> 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<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;}
这段代码比较抽象,但是我们可以看出,中间被订阅者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 RACCompoundDisposableInlineCountRACDisposable *inlineCopy[RACCompoundDisposableInlineCount];#endifCFArrayRef remainingDisposables = NULL;OSSpinLockLock(&_spinLock);{_disposed = YES;#if RACCompoundDisposableInlineCountfor (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {inlineCopy[i] = _inlineDisposables[i];_inlineDisposables[i] = nil;}#endifremainingDisposables = _disposables;_disposables = NULL;}OSSpinLockUnlock(&_spinLock);#if RACCompoundDisposableInlineCount// Dispose outside of the lock in case the compound disposable is used// recursively.for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {[inlineCopy[i] dispose];}#endifif (remainingDisposables == NULL) return;CFIndex count = CFArrayGetCount(remainingDisposables);CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL);CFRelease(remainingDisposables);}
在前面信号的订阅时,订阅者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<RACSubscriber> subscriber) {        NSLog(@"被订阅者代码执行");        [[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;    }];    //信号的订阅者    NSLog(@"Signal was created.");    [[RACScheduler mainThreadScheduler] afterDelay:1.0 schedule:^{        NSLog(@"订阅者代码执行");        [signal subscribeNext:^(id x) {            NSLog(@"小明 recveive: %@", x);        }];    }];}
在此我们通过延迟时间来模仿,小明收到的每月的读者,打印结果如下:
2017-12-04 19:12:35.517553+0800 MDProject[81121:3596317] Signal was created.
2017-12-04 19:12:43.528421+0800 MDProject[81121:3596317] 小明 recveive: 一月份的《读者》
2017-12-04 19:12:44.525774+0800 MDProject[81121:3596317] 小明 recveive: 二月份的《读者》
2017-12-04 19:12:45.528010+0800 MDProject[81121:3596317] 小明 recveive: 三月份的《读者》
2017-12-04 19:12:46.527258+0800 MDProject[81121:3596317] 小明 recveive: 四月份的《读者》
2017-12-04 19:12:47.525560+0800 MDProject[81121:3596317] 小明 recveive: 五月份的《读者》
2017-12-04 19:12:48.525488+0800 MDProject[81121:3596317] 小明 recveive: 六月份的《读者》
2017-12-04 19:12:49.527300+0800 MDProject[81121:3596317] 小明 recveive: 七月份的《读者》
2017-12-04 19:12:50.528087+0800 MDProject[81121:3596317] 小明 recveive: 八月份的《读者》
2017-12-04 19:12:51.528652+0800 MDProject[81121:3596317] 小明 recveive: 九月份的《读者》
2017-12-04 19:12:52.528625+0800 MDProject[81121:3596317] 小明 recveive: 十月份的《读者》
2017-12-04 19:12:53.526341+0800 MDProject[81121:3596317] 小明 recveive: 十一月份的《读者》
2017-12-04 19: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<RACSubscriber>)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<RACSubscriber> 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<RACSubscriber> subscriber) {[subscriber sendNext:value];}];}
- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {NSArray *subscribers;@synchronized (self.subscribers) {subscribers = [self.subscribers copy];}for (id<RACSubscriber> subscriber in subscribers) {block(subscriber);}}
以上代码不难看出,sendNext这一步会遍历订阅者数组,依次执行每个订阅者的block方法。
3、冷信号和热信号的区别
有了上面的例子,冷信号与热信号的区别,其实很明显了:
1)冷信号是一对一的,订阅者与被订阅者通过block绑定,没有订阅者,就没有被订阅者,有了订阅者代码执行,
被订阅者代码就会被完整地执行,所以冷信号给我们带来的是完整信号。
2)热信号的概念类似于Notification的概念,它不管某一个订阅者代码是否被执行,它都会去遍历订阅者数组,依次
执行被订阅者代码,如果你先到,你就可以“先拿到”较早的信号,如果你晚加入订阅者数组,你就只能被晚点遍历到,
接收到较晚的信号。

五、参考资料
函数式编程在Redux/React中的应用
https://tech.meituan.com/functional_programming_in_redux.html
细说ReactiveCocoa的冷信号与热信号(一)
https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-1.html
细说ReactiveCocoa的冷信号与热信号(二):为什么要区分冷热信号
https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-2.html

阅读全文
0 0
原创粉丝点击