ReactiveCocoa源码解读(二)
上一篇解读了ReactiveCocoa的三个重要的类的底层实现,本篇继续。
一、RACMulticastConnection
1.应用
RACMulticastConnection: 用于当一个信号被多次订阅时,为了保证创建信号时,避免多次调用创建信号的block造成副作用,可以使用该类处理,保证创建信号的block执行一次。
// 创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"发送请求");
[subscriber sendNext:@1];
return nil;
}];
// 创建连接
RACMulticastConnection *connect = [signal publish];
// 订阅连接的信号
[connect.signal subscribeNext:^(id x) {
NSLog(@"connect 第一次订阅信号: %@", x);
}];
[connect.signal subscribeNext:^(id x) {
NSLog(@"connect 第二次订阅信号: %@", x);
}];
// 连接
[connect connect];
2.源码实现
- 底层原理
1.创建connect,connect.sourceSignal -> RACSignal(原始信号) connect.signal -> RACSubject
2.订阅connect.signal,会调用RACSubject的subscribeNext,创建订阅者,而且把订阅者保存起来,不会执行block。
3.[connect connect]内部会订阅RACSignal(原始信号),并且订阅者是RACSubject
3.1.订阅原始信号,就会调用原始信号中的didSubscribe
3.2 didSubscribe,拿到订阅者调用sendNext,其实是调用RACSubject的sendNext
4.RACSubject的sendNext,会遍历RACSubject所有订阅者发送信号。
4.1 因为刚刚第二步,都是在订阅RACSubject,因此会拿到第二步所有的订阅者,调用他们的nextBlock
- 创建信号
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
// RACDynamicSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
//将代码块保存到信号里面(但此时仅仅是保存,没有调用),所以信号还是冷信号
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
- 创建连接
[signal publish]
// RACSignal+Operations.m
- (RACMulticastConnection *)publish {
// 创建订阅者
RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
// 创建connection,参数是刚才创建的订阅者
RACMulticastConnection *connection = [self multicast:subject];
return connection;
}
- (RACMulticastConnection *)multicast:(RACSubject *)subject {
[subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
return connection;
}
// RACMulticastConnection.m
- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
NSCParameterAssert(source != nil);
NSCParameterAssert(subject != nil);
self = [super init];
if (self == nil) return nil;
// 保存原始信号
_sourceSignal = source;
_serialDisposable = [[RACSerialDisposable alloc] init];
// 保存订阅者,即_signal是RACSubject对象
_signal = subject;
return self;
}
- 订阅信号
(RACDisposable *)subscribeNext:(void (^ )(id x))nextBlock;
// RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
// RACSubscriber.m
+ (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;
}
// RACSubject.m
- (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];
}
}];
}
- 连接信号
[connect connect];
// RACMulticastConnection.m
- (RACDisposable *)connect {
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect) {
// 订阅原生信号
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
// RACDynamicSignal.m
- (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;
}
// RACSubject.m
- (void)sendNext:(id)value {
// 遍历_subscribers数组,执行nextBlock
[self enumerateSubscribersUsingBlock:^(id subscriber) {
[subscriber sendNext:value];
}];
}
3.流程图

4.总结
RACMulticastConnection利用RACSubject实现了创建信号的block只执行一次的功能。对于需要对此订阅信号,但是不希望多次创建信号的应用场合,可以RACMulticastConnection解决。
二、RACCommand
1.应用
RACCommand类用来表示动作的执行, 是对动作触发后的连锁事件的封装。常用在封装网络请求,按钮点击等等场合。
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
if (/* DISABLES CODE */ (YES)) {
// 正常发送数据,必须发送完成信号
[subscriber sendNext:@"Smile"];
[subscriber sendCompleted];
} else {
// 发送错误信号
[subscriber sendError:[NSError errorWithDomain:@"Network failed" code:0005 userInfo:nil]];
}
// 信号被销毁前,做一些清理的工作;如果不需要,可以 return nil
return [RACDisposable disposableWithBlock:^{
NSLog(@"信号被销毁了");
}];
}];
}];
// 执行信号并订阅
[[command execute:nil] subscribeNext:^(id x) {
NSLog(@"receive data: %@", x);
}];
2.源码实现
RACCommand底层实现
1. 创建命令,保存signalBlock
2. 执行命令
* 2.1 调用signalBlock
* 2.2 创建connect,传入RACReplaySubject对象,然后连接信号
3. 订阅信号
* 3.1 创建订阅者,保存到RACReplaySubject对象的_subscribers数组中
* 3.2 遍历valuesReceived数组,调用订阅者发送数据
- 创建command
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
// RACCommand.m
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock {
return [self initWithEnabled:nil signalBlock:signalBlock];
}
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock {
NSCParameterAssert(signalBlock != nil);
self = [super init];
if (self == nil) return nil;
_activeExecutionSignals = [[NSMutableArray alloc] init];
// 保存创建信号的block
_signalBlock = [signalBlock copy];
......
}
- 执行command
- (RACSignal *)execute:(id)input
// RACCommand.m
- (RACSignal *)execute:(id)input {
// `immediateEnabled` is guaranteed to send a value upon subscription, so
// -first is acceptable here.
BOOL enabled = [[self.immediateEnabled first] boolValue];
if (!enabled) {
NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{
NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),
RACUnderlyingCommandErrorKey: self
}];
return [RACSignal error:error];
}
RACSignal *signal = self.signalBlock(input);
......
// 创建连接,用RACReplaySubject作为订阅者
RACMulticastConnection *connection = [[signal
subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]];
......
// 连接信号
[connection connect];
return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, [input rac_description]];
}
// RACMulticastConnection.m
- (RACDisposable *)connect {
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect) {
// 执行创建信号的block
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
- 订阅command
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
// RACReplaySubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
@synchronized (self) {
for (id value in self.valuesReceived) {
if (compoundDisposable.disposed) return;
// 调用订阅者,发送数据 "Smile"
[subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
}
if (compoundDisposable.disposed) return;
if (self.hasCompleted) {
[subscriber sendCompleted];
} else if (self.hasError) {
[subscriber sendError:self.error];
} else {
// 调用父类方法,保存订阅者到_subscribers数组
RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
[compoundDisposable addDisposable:subscriptionDisposable];
}
}
}];
[compoundDisposable addDisposable:schedulingDisposable];
return compoundDisposable;
}
3.流程图

4.总结
RACCommand用来封装事件时,还可以订阅信号(executionSignals)、订阅最新信号(switchToLatest)、跳过几次信号(skip)或信号是否正在执行(executing),在执行信号时,还可以监听错误信号和完成信号,请参考demo例子。
ReactiveCocoa框架的源码分析暂告一段落,如有分析不足之处,欢迎互相交流。
Demo地址:
ReactiveCocoa源码解读(二)的更多相关文章
- jQuery.Callbacks 源码解读二
一.参数标记 /* * once: 确保回调列表仅只fire一次 * unique: 在执行add操作中,确保回调列表中不存在重复的回调 * stopOnFalse: 当执行回调返回值为false,则 ...
- (转)go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin
转自:http://www.baiyuxiong.com/?p=886 ---------------------------------------------------------------- ...
- ReactiveCocoa源码解读(一)
本着饮水思源的想法,面对ReactiveCocoa的强大功能,按捺不住心中的好奇心,于是走进其源码之中,一探ReactiveCocoa的魅力所在.虽然,耳闻其强大功能的核心是:信号,但一直不知道这个信 ...
- ReactiveCocoa源码解析(二) Bag容器的代码实现
今天博客我接着上篇博客的内容来,上篇博客我们详细的看了ReactiveSwift中的Observer已经Event的代码实现.接下来我们来看一下ReactiveSwift中的结构体Bag的实现.Bag ...
- mybatis源码解读(二)——构建Configuration对象
Configuration 对象保存了所有mybatis的配置信息,主要包括: ①. mybatis-configuration.xml 基础配置文件 ②. mapper.xml 映射器配置文件 1. ...
- ConcurrentHashMap源码解读二
接下来就讲解put里面的三个方法,分别是 1.数组初始化方法initTable() 2.线程协助扩容方法helpTransfer() 3.计数方法addCount() 首先是数组初始化,再将源码之前, ...
- go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin
nsqlookupd: 官方文档解释见:http://bitly.github.io/nsq/components/nsqlookupd.html 用官方话来讲是:nsqlookupd管理拓扑信息,客 ...
- vue2.0 源码解读(二)
小伞最近比较忙,阅读源码的速度越来越慢了 最近和朋友交流的时候,发现他们对于源码的目录结构都不是很清楚 红色圈子内是我们需要关心的地方 compiler 模板编译部分 core 核心实现部分 ent ...
- ROS源码解读(二)--全局路径规划
博客转载自:https://blog.csdn.net/xmy306538517/article/details/79032324 ROS中,机器人全局路径规划默认使用的是navfn包 ,move_b ...
随机推荐
- 快学Scala之继承
## 1. 继承 Scala语言通过 extends 关键字来继承类. 那么继承一个类有什么好处呢? 子类除了拥有继承自超类的方法和字段(即为val(常量), var(变量)所定义的), 还可 ...
- 如何在一个Eclipse同时启动两个Tomcat
比如:有两个版本的tomcat,一个5.*,一个6.*,此时由于两个工程分别部署在两个版本的tomcat下,需要同时启动两个tomcat,以下是方法: 1.特别要注意: 不要设置CATALINA_HO ...
- UVa455 Periodic String 的wronganswer问题探讨,以及AC的新思路
题目的意思是一个字符串有某个长度为k的字符串通过不断重复形成的,而k被称为该字符串的周期.而我们所要做的是找出该字符串的最小周期. input The first line is an integer ...
- CJOJ 2485 UVa 11991 生日礼物 / UVa 11991 Easy Problem from Rujia Liu?
CJOJ 2485 UVa 11991 生日礼物 / UVa 11991 Easy Problem from Rujia Liu? Description (原题来自刘汝佳<训练指南>Pa ...
- 取一个整数a从右端开始的4~7位
题目:取一个整数a从右端开始的4-7位. 程序分析:可以这样考虑: (1)先使a右移4位. (2)设置一个低4位全为1,其余全为0的数.可用~(~0 < <4) (3)将上面二者进行&am ...
- 分享网上搜到的Oracle中对判定条件where 1=1的正解
今天在网上找到了Oracle中对判定条件where 1=1的正解,粘贴出来和大家分享下 1=1 是永恒成立的,意思无条件的,也就是说在SQL语句里有没有这个1=1都可以. 这个1=1常用于应用程序根据 ...
- Palindrome 回文数
回文数,从前到后,从后到前都一样 把数字转成字符串来处理 package com.rust.cal; public class Palindrome { public static boolean i ...
- 对于mariadb安装后可以默认使用无密码登录的问题解决方案
mariadb安装后默认可以无密码登录 所以首先我们要设置root用户的密码 mysqladmin -u root -p password 密码 [遇到enter直接enter就行,注意,如果是重新设 ...
- centos下安装并配置tomcat
1,安装jdk,centos默认安装了java-openjdk的环境,但是不带JDK,运行 yum install java-1.7.0-openjdk java-1.7.0-openjdk-deve ...
- NLP —— 图模型(二)条件随机场(Conditional random field,CRF)
本文简单整理了以下内容: (一)马尔可夫随机场(Markov random field,无向图模型)简单回顾 (二)条件随机场(Conditional random field,CRF) 这篇写的非常 ...