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

******阅读完此文,大概需要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官方地址:

二、信号的概念

介绍信号之前,不得不扯点函数响应式编程的概念,如下面:
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 RACCompoundDisposableInlineCount
	RACDisposable *inlineCopy[RACCompoundDisposableInlineCount];
	#endif
	CFArrayRef remainingDisposables = NULL;
	OSSpinLockLock(&_spinLock);
	{
		_disposed = YES;
		#if RACCompoundDisposableInlineCount
		for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
			inlineCopy[i] = _inlineDisposables[i];
			_inlineDisposables[i] = nil;
		}
		#endif
		remainingDisposables = _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];
	}
	#endif
	if (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中的应用
细说ReactiveCocoa的冷信号与热信号(一)
细说ReactiveCocoa的冷信号与热信号(二):为什么要区分冷热信号

猜你喜欢

转载自blog.csdn.net/lizitao/article/details/78721650
今日推荐