本着饮水思源的想法,面对ReactiveCocoa的强大功能,按捺不住心中的好奇心,于是走进其源码之中,一探ReactiveCocoa的魅力所在。虽然,耳闻其强大功能的核心是:信号,但一直不知道这个信号是如何产生、如何传递,又是如何被处理的。曾经以为信号传递是通知,但是真正读了源码后,才发现之前的想法有多不妥,而人家的实现又是多巧妙。

本文主要从ReactiveCocoa的主要类入手,通过剖析其整个应用过程中,信号的生命周期来领略其编程之美。

一、RACSignal

1. 应用

// 1.创建信号
RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 注:block在此仅仅是个参数,未被调用,
//当有订阅者订阅信号时会调用block。 // 2.发送信号
[subscriber sendNext:@1];
// 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
return nil;
}]; // 3.订阅信号,才会激活信号.
[siganl subscribeNext:^(id x) {
// block调用时刻:每当有信号发出数据,就会调用block.
NSLog(@"接收到数据:%@",x);
}];

2.源码实现

  • 创建信号

+(RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;

// RACDynamicSignal.m

+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {
//创建了一个RACDynamicSignal类的信号
RACDynamicSignal *signal = [[self alloc] init];
//将代码块保存到信号里面(但此时仅仅是保存,没有调用),所以信号还是冷信号
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
  • 订阅信号
(RACDisposable *)subscribeNext:(void (^ )(id x))nextBlock;
// RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
// 内部创建了RACSubscriber(订阅者)类的实例对象o,并且将nextBlock保存到o中,在返回值出执行o,实际也是执行了nextBlock。
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];
// 将block保存到subscriber中
subscriber->_next = [next copy];
subscriber->_error = [error copy];
subscriber->_completed = [completed copy]; return subscriber;
} // RACDynamicSignal.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil); RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
//判断有无self.didSubscribe,有则执行该self.didSubscribe,意味着将订阅者subscriber发送过去
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}]; [disposable addDisposable:schedulingDisposable];
} return disposable;
} // RACPassthroughSubscriber.m
- (instancetype)initWithSubscriber:(id)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
NSCParameterAssert(subscriber != nil); self = [super init];
if (self == nil) return nil;
// 保存订阅者,信号,处理操作
_innerSubscriber = subscriber;
_signal = signal;
_disposable = disposable; [self.innerSubscriber didSubscribeWithDisposable:self.disposable];
return self;
}
  • 发送信号
[subscriber sendNext:@1]
// RACPassthroughSubscriber.m
- (void)sendNext:(id)value {
if (self.disposable.disposed) return; if (RACSIGNAL_NEXT_ENABLED()) {
RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description]));
} [self.innerSubscriber sendNext:value];
} // RACSubscriber.m
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
// 名为next的block是返回值为void,参数为id类型的value,在sendNext:内部,将next复制给nextBlock,执行该方法后,subscribeNext:的block参数才会被调用。
nextBlock(value);
}
}

3.流程图

4.总结

先创建信号,然后订阅信号,最后执行didSubscribe内部的方法,顺序是不能变的

RACSignal底层实现

* 1.创建信号,首先把didSubscribe保存到信号中,还不会触发。
* 2.当信号被订阅,也就是调用signal的subscribeNext:nextBlock
2.1 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
2.2 subscribeNext内部会调用siganl的didSubscribe
* 3.siganl的didSubscribe中调用[subscriber sendNext:@1];
3.1 sendNext底层其实就是执行subscriber的nextBlock

二、RACSubject

1. 应用

// 创建信号
RACSubject *subject = [RACSubject subject]; // 订阅
[subject subscribeNext:^(id x) {
NSLog(@"第一个订阅者:%@", x);
}]; // 发送信号
[subject sendNext:@"1"];

2.源码实现

  • 创建信号
// RACSubject.m
+ (instancetype)subject {
return [[self alloc] init];
} - (id)init {
self = [super init];
if (self == nil) return nil; _disposable = [RACCompoundDisposable compoundDisposable];
_subscribers = [[NSMutableArray alloc] initWithCapacity:1]; return self;
}
  • 订阅信号

RACSubject订阅信号的实质就是将内部创建的订阅者保存在订阅者数组self.subscribers中,仅此而已。订阅者对象有一个名为nextBlock的block参数

// 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];
}
}];
}
  • 发送信号
底层实现:
1. 先遍历订阅者数组中的订阅者;
2. 后执行订阅者中的nextBlock;
3. 最后让订阅者发送信号
// RACSubject.m
- (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);
}
}

3.流程图

4.总结

RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。

RACDisposable:用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。

RACSubject的底层与RACSignal不一样:

  • 调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了
  • 调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock
  • 由于本质是将订阅者保存到数组中,所以可以有多个订阅者订阅信息。

必须先订阅,后发送信息。订阅信号就是创建订阅者的过程,如果不先订阅,数组中就没有订阅者对象,那就通过订阅者发送消息

三、RACReplaySubject

1.应用

RACReplaySubject即可以先订阅后发送信号,也可以反过来

RACReplaySubject *subject = [RACReplaySubject subject];

// 第一次订阅
[subject subscribeNext:^(id x) {
NSLog(@"%@", x);
}]; // 发送信号
[subject sendNext:@"1"]; // 第一次订阅
[subject subscribeNext:^(id x) {
NSLog(@"%@", x);
}];

2.源码实现

  • 创建信号
// RACSubject.m
+ (instancetype)subject {
return [[self alloc] init];
} // RACReplaySubject.m
- (instancetype)init {
return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity];
} - (instancetype)initWithCapacity:(NSUInteger)capacity {
self = [super init];
if (self == nil) return nil; _capacity = capacity;
// 会用这个数组保存值value
_valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]); return self;
}
  • 订阅信号
// 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; [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 {
RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
[compoundDisposable addDisposable:subscriptionDisposable];
}
}
}]; [compoundDisposable addDisposable:schedulingDisposable]; return compoundDisposable;
}
  • 发送信号
// RACReplaySubject.m
- (void)sendNext:(id)value {
@synchronized (self) {
//重点:发送信号的时候,会先将值value保存到数组中
[self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
//调用父类发送(先遍历订阅者,然后发送值value)
[super sendNext:value]; if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
[self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
}
}
}

3.原理图

4.总结

RACReplaySubjectRACSubject的子类

由于每次发送信号时,会先保存nextBlock,然后调用父类的sendNext方法,遍历订阅者,执行信号;而每次订阅信号时,会从valuesReceived中取值,然后调用sendNext方法,遍历订阅者,执行信号。所以,订阅和发送没有先后顺序。


未完待续 ......

ReactiveCocoa源码解读(一)的更多相关文章

  1. ReactiveCocoa源码解读(二)

    上一篇解读了ReactiveCocoa的三个重要的类的底层实现,本篇继续. 一.RACMulticastConnection 1.应用 RACMulticastConnection: 用于当一个信号被 ...

  2. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  3. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  4. SDWebImage源码解读 之 UIImage+GIF

    第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...

  5. SDWebImage源码解读 之 SDWebImageCompat

    第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...

  6. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  7. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

  8. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

  9. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

随机推荐

  1. Linux程序设计之shell程序设计

    看了<linux程序设计(第4版)>(作者:Neil Matthew ,Richard Stones ,陈建 ,宋健建译).做个笔记,以备后面查看. 首先,清楚几个概念. shell编程属 ...

  2. win7-x64安装mysql5.7.11(官方zip版)

    1.下载官方安装包(http://www.mysql.com/downloads/),此zip包是没有安装器的(*.msi),也没有辅助配置的自动程序. 2.解压zip包,将文件放入指定目录,如:D: ...

  3. 【原】无脑操作:Windows 10 + MySQL 5.5 安装使用及免安装使用

    本文介绍Windows 10环境下, MySQL 5.5的安装使用及免安装使用 资源下载: MySQL安装文件:http://download.csdn.net/detail/lf19820717/9 ...

  4. voa 2015 / 4 / 25

    When English speakers talk about time and place, there are three little words that often come up: in ...

  5. Servlet端 接收不到4096,8192长度的JSON参数

    Servlet端的日志显示,客户端传过来的JSON参数是空值. 但是在客户端的日志显示,已将JSON参数传送过去. 经调查发现,加减1位后的JSON参数均可以正常传送. 只有8192,4096长度的J ...

  6. java基础06 IO流

    IO用于在设备间进行数据传输的操作. Java IO流类图结构:   IO流分类 字节流: InputStream FileInputStream BufferedInputStream Output ...

  7. SQL Server2008 安装及概述

    最近在学习SQL Server 数据库,用的版本是2008 R2的版本,如下图所示,本人学习时间不长,写这篇文章一是为了锻炼自己加深印象,二也可以与和诸多朋友进行交流以及得到大家的指教. 安装教程网上 ...

  8. tensorflow softplus应用

    1.softplus函数表达式 图像: 2.tensorflow 举例 import tensorflow as tf input=tf.constant([0,1,2,3],dtype=tf.flo ...

  9. 面向接口编程详解-Java篇

    相信看到这篇文字的人已经不需要了解什么是接口了,我就不再过多的做介绍了,直接步入正题,接口测试如何编写.那么在这一篇里,我们用一个例子,让各位对这个重要的编程思想有个直观的印象.为充分考虑到初学者,所 ...

  10. (转)Java线程:线程的同步与锁

      Java线程:线程的同步与锁       一.同步问题提出   线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Fo ...