最近事情很多, 前段时间因为在排查APP中出现的一些诡异crash, 所以抽空学了一些汇编和逆向的东西, RAC这块就落下了, 虽然偶尔也在看, 但是没法系统整理, 今天算在抽出时间了, 所以把Signal部分的代码分析分析, 后续再把其它部分补上. 还是老话, 中间有错漏 , 还希望指正.
RAC的信号世界主要由以下三个核心组件组成:
RACSignal, RACSubscriber和RACDisposable. (调度器RACScheduler算是一些辅助功能, 不在核心链路中.)
Subscriber可以订阅(subscribe)Signal
Disposable可以清除(dispose)Subscriber对Signal的订阅(以及相关资源占用)
所以先从Signal开始看, 尽量屏蔽掉其它2个部分的干扰:
RACDynamicSignal
Signal的子类, crateSignal本质上就是返回这个类的实例.
功能:
- 创建信号, 存储订阅block
- 管理订阅关系
// 1. 直接新建一个对象, 把didSubscribe的block存储起来
+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
// 2. 返回一个实际上为RACCompoundDisposable的Disposable对象.
// RACCompoundDisposable是组合多个Disposable对象, 并且可以一次性管理的类, 一次disposable全部清除.
- (RACDisposable *)subscribe:(id)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
// RACPassthroughSubscriber在订阅部分再细讲, 这里按下不表
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
// 订阅后在这里触发self.didSubscribe的block
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
// 2.1 添加调度器的disposable
[disposable addDisposable:innerDisposable];
}];
// 2.2 添加信号本身的disposable
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
RACErrorSignal, RACEmptySignal, RACReturnSignal
RACErrorSignal在订阅后直接返回Error;
RACEmptySignal在订阅后直接返回Completed;
RACReturnSignal在订阅后先sendNext:returnValue,然后sendCompleted.
RACSubject
signal的子类, 是热信号(冷热信号参考译文: 框架总览和设计指导)
功能:
- 本身可以看做一个信号
- 还可以发送事件
从代码上来看, RACSubject还持有了2个"数组", 一个是subscribers, 一个则是disposable(RACCompoundDisposable类型)
- (id)init {
self = [super init];
if (self == nil) return nil;
_disposable = [RACCompoundDisposable compoundDisposable];
_subscribers = [[NSMutableArray alloc] initWithCapacity:1];
return self;
}
- (void)dealloc {
[self.disposable dispose];
}
所以在发送事件的时候, 也会有遍历的过程:
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id subscriber) {
[subscriber sendNext:value];
}];
}
- (void)sendError:(NSError *)error {
[self.disposable dispose];
[self enumerateSubscribersUsingBlock:^(id subscriber) {
[subscriber sendError:error];
}];
}
- (void)sendCompleted {
[self.disposable dispose];
[self enumerateSubscribersUsingBlock:^(id subscriber) {
[subscriber sendCompleted];
}];
}
- (void)enumerateSubscribersUsingBlock:(void (^)(id subscriber))block {
NSArray *subscribers;
@synchronized (self.subscribers) {
subscribers = [self.subscribers copy];
}
for (id subscriber in subscribers) {
block(subscriber);
}
}
重点是subscribe:方法:
- (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];
}
// 返回一个Disposable, 在调用Disposable时移出订阅者
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];
}
}];
}
Subject还有RACReplaySubject, RACGroupedSignal和RACBehaviorSubject子类, 这里先看看其功能, 后期再补上代码分析吧.
RACReplaySubject是存储了信号sendNext的值, 然后每次订阅都自动重放这些值.
RACGroupedSignal只是给subject一个key值, 来做分组(Signal的操作有个groupBy)
RACBehaviorSubject订阅后发送最后收到的一个值
RACSignal
常规方法还有(不包括operation里面的):
concat: ,zipWith: ,startLazilyWithScheduler: (startEagerlyWithScheduler就是直接调用了Lazily然后强制立即执行), 以及最核心的bind:
concat:
concat与Sequence里面的concat类似, 都是把对象串起来.
- (RACSignal *)concat:(RACSignal *)signal {
return [[RACSignal createSignal:^(id subscriber) {
// RACSerialDisposable这个类型的Disposable, 会在每次赋值的时候, 判断之前的值是否已经dispose, 如果是就对之前的值调用dispose, 所以这里会体现为, 一个dispose则后续都会被dispose掉
RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init];
// 把自己的事件传递给新的订阅者
RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
// 在第一个信号完成的时候着手处理第二个信号, 因此是串起来的
RACDisposable *concattedDisposable = [signal subscribe:subscriber];
// 这里会对sourceDisposable调用dispose
serialDisposable.disposable = concattedDisposable;
}];
serialDisposable.disposable = sourceDisposable;
// 最后返回给调用方 决定concattedDisposable的归宿
return serialDisposable;
}] setNameWithFormat:@"[%@] -concat: %@", self.name, signal];
}
zip:
zip同样与RACSequence里面的zip类似, 只是把直接合成Tuple改为sendNext的时候把2个信号的值合成了一个Tuple, 注意, 只有一个信号sendNext的时候, 合成的信号是不会sendNext的:
整体代码略长, 但是大部分都是"复制"的:
- (RACSignal *)zipWith:(RACSignal *)signal {
NSCParameterAssert(signal != nil);
return [[RACSignal createSignal:^(id subscriber) {
// 标志位和存储后备值数据的数组
__block BOOL selfCompleted = NO;
NSMutableArray *selfValues = [NSMutableArray array];
__block BOOL otherCompleted = NO;
NSMutableArray *otherValues = [NSMutableArray array];
// 在一个信号完成的时候, 判断是否要对结合信号发送完成事件
// 完成条件: 存在一个信号已经完成且没有后备值了
void (^sendCompletedIfNecessary)(void) = ^{
@synchronized (selfValues) {
BOOL selfEmpty = (selfCompleted && selfValues.count == 0);
BOOL otherEmpty = (otherCompleted && otherValues.count == 0);
if (selfEmpty || otherEmpty) [subscriber sendCompleted];
}
};
// next事件必须两者都有后备值, 命名应该也需要加上IfNecessary
void (^sendNext)(void) = ^{
@synchronized (selfValues) {
if (selfValues.count == 0) return;
if (otherValues.count == 0) return;
// 把后备值数组第一个取出来合成Tuple 发送给调用方, 然后移出之
RACTuple *tuple = RACTuplePack(selfValues[0], otherValues[0]);
[selfValues removeObjectAtIndex:0];
[otherValues removeObjectAtIndex:0];
[subscriber sendNext:tuple];
// 判断是否需要完成
sendCompletedIfNecessary();
}
};
RACDisposable *selfDisposable = [self subscribeNext:^(id x) {
@synchronized (selfValues) {
[selfValues addObject:x ?: RACTupleNil.tupleNil];
sendNext();
}
} error:^(NSError *error) {
// error事件会直接传递出去
[subscriber sendError:error];
} completed:^{
@synchronized (selfValues) {
selfCompleted = YES;
sendCompletedIfNecessary();
}
}];
RACDisposable *otherDisposable = [signal subscribeNext:^(id x) {
@synchronized (selfValues) {
[otherValues addObject:x ?: RACTupleNil.tupleNil];
sendNext();
}
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
@synchronized (selfValues) {
otherCompleted = YES;
sendCompletedIfNecessary();
}
}];
// 显然在dispose的时候是两者都需要的, 下面的代码也符合预期
// 但是, 有个疑问是, 为什么不用CompoundDisposable???
// 11.29更新: 两者需要同时清理, 所以不用CompoundDisposable
return [RACDisposable disposableWithBlock:^{
[selfDisposable dispose];
[otherDisposable dispose];
}];
}] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal];
}
startLazilyWithScheduler:block:
核心在于lazily, 因此可以把Scheduler忽略掉.
+ (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id subscriber))block {
NSCParameterAssert(scheduler != nil);
NSCParameterAssert(block != NULL);
// 虽然写在前面, 但是其实是第二部分, 先看下面return部分 再看这边
// 创建一个RACMulticastConnection, 在调用connect的时候才会"激活", 所以是lazily的
RACMulticastConnection *connection = [[RACSignal
createSignal:^ id (id subscriber) {
// 创建一个信号, 直接调用参数的block
block(subscriber);
return nil;
}]
// 转换为replaySubject, 这种类型的subject可以重放事件
// 之所以用这种subject是因此普通的Signal就是"重放式"的, 而一般的RACMulticastConnection则不是, 具体参考框架总览篇
multicast:[RACReplaySubject subject]];
// 返回一个信号, 在收到订阅后把订阅者传递给源信号(也就是上面创建的信号)
return [[[RACSignal
createSignal:^ id (id subscriber) {
[connection.signal subscribe:subscriber];
// 调用connect才"激活"信号(本质上就是订阅之)
// 所以eagerly的方法会在返回该信号后转换为RACMulticastConnection 后直接connect, 以达到eagerly的目的
[connection connect];
return nil;
}]
// 调度到指定的调度器
subscribeOn:scheduler]
setNameWithFormat:@"+startLazilyWithScheduler: %@ block:", scheduler];
}
bind:
bind涉及到很多的Signal Operation, 这里的内容比较丰富, 但是都可以和Sequence在概念上映射上, 而且不影响核心链路, 所以先不看了, 到时候再另开一篇专门来看好了.
RACMulticastConnection
从头文件对这个类的解释是:
A multicast connection encapsulates the idea of sharing one subscription to a
signal to many subscribers. This is most often needed if the subscription to
the underlying signal involves side-effects or shouldn't be called more than
once.
Multicast可以翻译为多播(和广播broadcast类似, 只不过multicast是有指向性的)
所以上面的话核心的意思是: 在多个订阅者间共享同一个订阅关系, 以此来使得副作用只被触发一次. 也就是说, 看起来是订阅了多次, 但是实际上只会由RACMulticastConnection对源信号订阅一次, 所有的订阅者实际上是订阅了RACMulticastConnection对象
先看看RACMulticastConnection的头文件:
// RACMulticastConnection.h
@property (nonatomic, strong, readonly) RACSignal *signal;
- (RACDisposable *)connect;
- (RACSignal *)autoconnect;
// RACMulticastConnection+Private.h
- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject;
持有了一个信号, 提供了1个私有的初始化方法和2个方法公共的connect方法, 而且看起来都是有同样作用的方法, 只是返回值类型有所区别.
直接从代码层面来看看原理:
私有属性:
@interface RACMulticastConnection () {
RACSubject *_signal; // 对外是一个signal 内部会转换为Subject, 因为connection既要接受其它Subscriber的订阅, 又要订阅源信号, 且保证了副作用只被触发一次
int32_t volatile _hasConnected; // 标记是否已经connect
}
@property (nonatomic, readonly, strong) RACSignal *sourceSignal;
// 对这里使用RACSerialDisposable而不是普通类型的Disposable应该是为了更防止内存泄露. 在调用方使用connect方法获取Disposable进行另外的复制后也可以保证资源被合理清除
@property (strong) RACSerialDisposable *serialDisposable;
提供的初始化方法没有什么特别的, 只是属性初始化而已, 所以直接看connect和autoconnect:
- (RACDisposable *)connect {
// 判断是否已经connect过
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect) {
// 需要connect则直接订阅这个信号
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
// 返回disposable
return self.serialDisposable;
}
- (RACSignal *)autoconnect {
// volatile是关闭编译器的的相关优化, 每次取subscriberCount值都从其地址取
__block volatile int32_t subscriberCount = 0;
// 不直接订阅"源信号", 而是创建一个信号, 在有新订阅后自动进行connect
return [[RACSignal
createSignal:^(id subscriber) {
OSAtomicIncrement32Barrier(&subscriberCount);
// 转换为对subject的订阅
RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];
// 触发对源信号的订阅(如果还未)
RACDisposable *connectionDisposable = [self connect];
return [RACDisposable disposableWithBlock:^{
[subscriptionDisposable dispose];
// 所有的订阅者都已经dispose后再dispose connect本身
if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {
[connectionDisposable dispose];
}
}];
}]
setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}
11.29补充:RACSubject子类们
RACReplaySubject
前面讲RACSubject的时候说了它是热信号, 它的didSubscribeBlock只会被触发一次, 后续再订阅就不会被触发了, 如果有需要仍然要触发, 那么就要用到它的子类RACReplaySubject
先看看头文件:
// 最多存储几条记录, 后面的会挤掉前面的
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity;
很简单, 就一个返回实例的类方法来控制记录的数量, 如果不传, 默认是NSUIntegerMax这么多个, 可以认为是无限多个.
再看看私有属性:
@interface RACReplaySubject ()
// 只读属性, 初始化之后就不允许修改了
@property (nonatomic, assign, readonly) NSUInteger capacity;
// 有一个存储收到值的数组, sendNext的值都会存在这里
@property (nonatomic, strong, readonly) NSMutableArray *valuesReceived;
@property (nonatomic, assign) BOOL hasCompleted;
@property (nonatomic, assign) BOOL hasError;
@property (nonatomic, strong) NSError *error;
@end
先看看发送事件的处理吧:
- (void)sendNext:(id)value {
@synchronized (self) {
// 保留sendNext的值记录
[self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
[super sendNext:value];
// 如果有负载上线, 且已经超过负载则删除多余的内容
// 作者选用了更加保险的措施, 一般来说应该remove掉第一个就够用了
// 难道是为了防止调用方用KVC的形式来修改出现一些问题么???
if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
[self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
}
}
}
// completed和error都是多设置了一下标志位
- (void)sendCompleted {
@synchronized (self) {
self.hasCompleted = YES;
[super sendCompleted];
}
}
- (void)sendError:(NSError *)e {
@synchronized (self) {
self.hasError = YES;
self.error = e;
[super sendError:e];
}
}
再看看订阅的方法:
- (RACDisposable *)subscribe:(id)subscriber {
// 组合调度器的disposable和订阅返回的disposable
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
// scheduler的代码之后再看吧, 反正就是在后台线程中执行block
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
@synchronized (self) {
// 如果未dispose 把之前记录的值都发给订阅者
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 {
// 执行订阅block, 添加disposable
RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
[compoundDisposable addDisposable:subscriptionDisposable];
}
}
}];
[compoundDisposable addDisposable:schedulingDisposable];
return compoundDisposable;
}
RACBehaviorSubject与RACSubject
两者都比较简单, 都是RACSubject的精简版, RACBehaviorSubject的是sendNext一直都只发送一个值, 直接看源码:
- (RACDisposable *)subscribe:(id)subscriber {
RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
@synchronized (self) {
// 订阅后立即发送该值
[subscriber sendNext:self.currentValue];
}
}];
// 这里没有使用CompoundDisposable, 原本我以为只是不同的人偏好不同, 但是看了下, 和上面的ReplySubject作者是同一个人, 所以之后要好好看看为什么...(问了一下作者本人, 回答是这两者没有什么本质区别)
return [RACDisposable disposableWithBlock:^{
[subscriptionDisposable dispose];
[schedulingDisposable dispose];
}];
}
- (void)sendNext:(id)value {
@synchronized (self) {
self.currentValue = value;
[super sendNext:value];
}
}
附上作者本人的回答:
我想仅仅是因为它(RACBehaviorSubject)太老了. RACBehaviorSubject写在RACCompoundDisposable之前, 而且它也不需要RACCompoundDisposable的任何功能, 所以也就没有必要去更新它了.
而RACGroupedSignal就更加简单了, 比Subject多加了一个key的属性作为自己的唯一标识
+ (instancetype)signalWithKey:(id)key {
RACGroupedSignal *subject = [self subject];
subject.key = key;
return subject;
}
等operation的时候再看它的具体作用好了.
10.2补充: RACChannel
channel 是有挺有意思的概念, 按照头文件来说, channel 可以看做是一个双向的 connection, 先看段测试代码来了解一下具体有什么作用吧:
- (void)testChannel
{
RACChannel *channel = [[RACChannel alloc] init];
[channel.leadingTerminal subscribeNext:^(id x) {
NSLog(@"leading: %@", x);
}];
[channel.followingTerminal subscribeNext:^(id x) {
NSLog(@"following: %@", x);
}];
[channel.leadingTerminal sendNext:@"1"];
[channel.followingTerminal sendNext:@"2"];
}
// 输出为:
following: 1
leading: 2
可想而知, leading 发送的事件会传递给 following, following 发送的会传递给 leading.