ReactiveCocoa理解
2017-04-28 10:27
1011 查看
ReactiveCocoa 与信号
ReactiveCocoa 对于状态的理解与《失控》一书中十分类似,将原有的各种设计模式,包括代理、Target/Action、block、通知中心以及观察者模式各种『输入』,都抽象成了数据流或者信号(也可以理解为状态流)让单一的组件能够对自己的响应动作进行控制,简化了视图控制器的负担。在 ReactiveCocoa 中最重要的信号,也就是
RACSignal对象是这一篇文章介绍的核心;文章中主要会介绍下面的代码片段出现的内容:
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) { [subscriber sendNext:@1]; [subscriber sendNext:@2]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"dispose"); }]; }]; [signal subscribeNext:^(id _Nullable x) { NSLog(@"%@", x); }];
在上述代码执行时,会在控制台中打印出以下内容:
1 2 dispose
代码片段基本都是围绕
RACSignal类进行的,文章会分四部分对上面的代码片段的工作流程进行简单的介绍:
简单了解
RACSignal
信号的创建
信号的订阅与发送
订阅的回收过程
RACSignal 简介
RACSignal其实是抽象类
RACStream的子类,在整个
ReactiveObjc 工程中有另一个类
RACSequence也继承自抽象类
RACStream:
RACSignal-Hierachy
RACSignal可以说是 ReactiveCocoa 中的核心类,也是最重要的概念,整个框架围绕着
RACSignal的概念进行组织,对
RACSignal最简单的理解就是它表示一连串的状态:
What-is-RACSigna
在状态改变时,对应的订阅者
RACSubscriber就会收到通知执行相应的指令,在 ReactiveCocoa
的世界中所有的消息都是通过信号的方式来传递的,原有的设计模式都会简化为一种模型,这篇文章作为 ReactiveCocoa 系列的第一篇文章并不会对这些问题进行详细的展开和介绍,只会对
RACSignal使用过程的原理进行简单的分析。
这一小节会对
RACStream以及
RACSignal中与
RACStream相关的部分进行简单的介绍。
RACStream
RACStream作为抽象类本身不提供方法的实现,其实现内部原生提供的而方法都是抽象方法,会在调用时直接抛出异常:
+ (__kindof RACStream *)empty { NSString *reason = [NSString stringWithFormat:@"%@ must be overridden by subclasses", NSStringFromSelector(_cmd)]; @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:nil]; } - (__kindof RACStream *)bind:(RACStreamBindBlock (^)(void))block; + (__kindof RACStream *)return:(id)value; - (__kindof RACStream *)concat:(RACStream *)stream; - (__kindof RACStream *)zipWith:(RACStream *)stream;
RACStream-AbstractMethod
上面的这些抽象方法都需要子类覆写,不过
RACStream在
Operations分类中使用上面的抽象方法提供了丰富的内容,比如说
-flattenMap:方法:
- (__kindof RACStream *)flattenMap:(__kindof RACStream * (^)(id value))block { Class class = self.class; return [[self bind:^{ return ^(id value, BOOL *stop) { id stream = block(value) ?: [class empty]; NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream); return stream; }; }] setNameWithFormat:@"[%@] -flattenMap:", self.name]; }
其他方法比如
-skip:、
-take:、
-ignore:等等实例方法都构建在这些抽象方法之上,只要子类覆写了所有抽象方法就能自动获得所有的
Operation分类中的方法。
RACStream-Operation
RACSignal 与 Monad
如果你对 Monad 有所了解,那么你应该知道 bind和
return其实是
Monad 中的概念,但 Monad 并不是本篇文章所覆盖的内容,并不会具体解释它到底是什么。
ReactiveCocoa 框架中借鉴了很多其他平台甚至语言中的概念,包括微软中的 Reactive Extension 以及 Haskell 中的 Monad,
RACStream提供的抽象方法中的
+return:和
-bind:就与
Haskell 中 Monad 完全一样。
很多人都说 Monad 只是一个自函子范畴上的一个幺半群而已;在笔者看来这种说法虽然是正确的,不过也很扯淡,这句话解释了还是跟没解释一样,如果有人再跟你用这句话解释 Monad,我觉得你最好的回应就是买一本范畴论糊他一脸。如果真的想了解 Haskell 中的 Monad 到底是什么?可以从代码的角度入手,多写一些代码就明白了,这个概念理解起来其实根本没什么困难的,当然也可以看一下 A
Fistful of Monads,写写其中的代码,会对 Monad 有自己的认知,当然,请不要再写一篇解释 Monad 的教程了(手动微笑)。
首先来看一下
+return方法的 实现:
+ (RACSignal *)return:(id)value { return [RACReturnSignal return:value]; }
该方法接受一个
NSObject对象,并返回一个
RACSignal的实例,它会将一个
UIKit 世界的对象
NSObject转换成 ReactiveCocoa 中的
RACSignal:
RACSignal-Return
而
RACReturnSignal也仅仅是把
NSObject对象包装一下,并没有做什么复杂的事情:
+ (RACSignal *)return:(id)value { RACReturnSignal *signal = [[self alloc] init]; signal->_value = value; return signal; }
但是
-bind:方法的 实现 相比之下就十分复杂了:
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block { return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { RACSignalBindBlock bindingBlock = block(); return [self subscribeNext:^(id x) { BOOL stop = NO; id signal = bindingBlock(x, &stop); if (signal != nil) { [signal subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ [subscriber sendCompleted]; }]; } if (signal == nil || stop) { [subscriber sendCompleted]; } } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ [subscriber sendCompleted]; }]; }] setNameWithFormat:@"[%@] -bind:", self.name]; }
笔者在这里对
-bind:方法进行了大量的省略,省去了其中对各种
RACDisposable的处理过程。
-bind:方法会在原信号每次发出消息时,都执行
RACSignalBindBlock对原有的信号中的消息进行变换生成一个新的信号:
RACSignal-Bind
在原有的
RACSignal对象上调用
-bind:方法传入
RACSignalBindBlock,图示中的右侧就是具体的执行过程,原信号在变换之后变成了新的蓝色的
RACSignal对象。
RACSignalBindBlock可以简单理解为一个接受
NSObject对象返回
RACSignal对象的函数:
typedef RACSignal * _Nullable (^RACSignalBindBlock)(id _Nullable value, BOOL *stop);
其函数签名可以理解为
id -> RACSignal,然而这种函数是无法直接对
RACSignal对象进行变换的;不过通过
-bind:方法就可以使用这种函数操作
RACSignal,其实现如下:
将
RACSignal对象『解包』出
NSObject对象;
将
NSObject传入
RACSignalBindBlock返回
RACSignal。
如果在不考虑
RACSignal会发出错误或者完成信号时,
-bind:可以简化为更简单的形式:
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block { return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { RACSignalBindBlock bindingBlock = block(); return [self subscribeNext:^(id x) { BOOL stop = NO; [bindingBlock(x, &stop) subscribeNext:^(id x) { [subscriber sendNext:x]; }]; }]; }] setNameWithFormat:@"[%@] -bind:", self.name]; }
调用
-subscribeNext:方法订阅当前信号,将信号中的状态解包,然后将原信号中的状态传入
bindingBlock中并订阅返回的新的信号,将生成的新状态
x传回原信号的订阅者。
这里通过两个简单的例子来了解
-bind:方法的作用:
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) { [subscriber sendNext:@1]; [subscriber sendNext:@2]; [subscriber sendNext:@3]; [subscriber sendNext:@4]; [subscriber sendCompleted]; return nil; }]; RACSignal *bindSignal = [signal bind:^RACSignalBindBlock _Nonnull{ return ^(NSNumber *value, BOOL *stop) { value = @(value.integerValue * value.integerValue); return [RACSignal return:value]; }; }]; [signal subscribeNext:^(id _Nullable x) { NSLog(@"signal: %@", x); }]; [bindSignal subscribeNext:^(id _Nullable x) { NSLog(@"bindSignal: %@", x); }];
上面的代码中直接使用了
+return:方法将
value打包成了
RACSignal *对象:
Before-After-Bind-RACSigna
在 BindSignal 中的每一个数字其实都是由一个
RACSignal包裹的,这里没有画出,在下一个例子中,读者可以清晰地看到其中的区别。
上图简要展示了变化前后的信号中包含的状态,在运行上述代码时,会在终端中打印出:
signal: 1 signal: 2 signal: 3 signal: 4 bindSignal: 1 bindSignal: 4 bindSignal: 9 bindSignal: 16
这是一个最简单的例子,直接使用
-return:打包
NSObject返回一个
RACSignal,接下来用一个更复杂的例子来帮助我们更好的了解
-bind:方法:
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) { [subscriber sendNext:@1]; [subscriber sendNext:@2]; [subscriber sendCompleted]; return nil; }]; RACSignal *bindSignal = [signal bind:^RACSignalBindBlock _Nonnull{ return ^(NSNumber *value, BOOL *stop) { NSNumber *returnValue = @(value.integerValue * value.integerValue); return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) { for (NSInteger i = 0; i < value.integerValue; i++) [subscriber sendNext:returnValue]; [subscriber sendCompleted]; return nil; }]; }; }]; [bindSignal subscribeNext:^(id _Nullable x) { NSLog(@"%@", x); }];
下图相比上面例子中的图片更能精确的表现出
-bind:方法都做了什么:
Before-After-Bind-RACSignal-Complicated
信号中原有的状态经过
-bind:方法中传入
RACSignalBindBlock的处理实际上返回了多个
RACSignal。
在源代码的注释中清楚地写出了方法的实现过程:
订阅原信号中的值;
将原信号发出的值传入
RACSignalBindBlock进行转换;
如果
RACSignalBindBlock返回一个信号,就会订阅该信号并将信号中的所有值传给订阅者
subscriber;
如果
RACSignalBindBlock请求终止信号就会向原信号发出
-sendCompleted消息;
当所有信号都完成时,会向订阅者发送
-sendCompleted;
无论何时,如果信号发出错误,都会向订阅者发送
-sendError:消息。
如果想要了解
-bind:方法在执行的过程中是如何处理订阅的清理和销毁的,可以阅读文章最后的 -bind:
中对订阅的销毁 部分。
信号的创建
信号的创建过程十分简单,-createSignal:是推荐的创建信号的方法,方法其实只做了一次转发:
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { return [RACDynamicSignal createSignal:didSubscribe]; } + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { RACDynamicSignal *signal = [[self alloc] init]; signal->_didSubscribe = [didSubscribe copy]; return [signal setNameWithFormat:@"+createSignal:"]; }
该方法其实只是创建了一个
RACDynamicSignal实例并保存了传入的
didSubscribe代码块,在每次有订阅者订阅当前信号时,都会执行一遍,向订阅者发送消息。
RACSignal 类簇
虽然 -createSignal:的方法签名上返回的是
RACSignal对象的实例,但是实际上这里返回的是
RACDynamicSignal,也就是
RACSignal的子类;同样,在
ReactiveCocoa 中也有很多其他的
RACSignal子类。
使用类簇的方式设计的
RACSignal在创建实例时可能会返回
RACDynamicSignal、
RACEmptySignal、
RACErrorSignal和
RACReturnSignal对象:
RACSignal-Subclasses
其实这几种子类并没有对原有的
RACSignal做出太大的改变,它们的创建过程也不是特别的复杂,只需要调用
RACSignal不同的类方法:
RACSignal-Instantiate-Object
RACSignal只是起到了一个代理的作用,最后的实现过程还是会指向对应的子类:
+ (RACSignal *)error:(NSError *)error {
return [RACErrorSignal error:error];
}
+ (RACSignal *)empty {
return [RACEmptySignal empty];
}
+ (RACSignal *)return:(id)value { return [RACReturnSignal return:value]; }
以
RACReturnSignal的创建过程为例:
+ (RACSignal *)return:(id)value { RACReturnSignal *signal = [[self alloc] init]; signal->_value = value; return signal; }
这个信号的创建过程和
RACDynamicSignal的初始化过程一样,都非常简单;只是将传入的
value简单保存一下,在有其他订阅者
-subscribe:时,向订阅者发送
value:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { return [RACScheduler.subscriptionScheduler schedule:^{ [subscriber sendNext:self.value]; [subscriber sendCompleted]; }]; }
RACEmptySignal和
RACErrorSignal的创建过程也异常的简单,只是对传入的数据进行简单的存储,然后在订阅时发送出来:
// RACEmptySignal + (RACSignal *)empty { return [[[self alloc] init] setNameWithFormat:@"+empty"]; } - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { return [RACScheduler.subscriptionScheduler schedule:^{ [subscriber sendCompleted]; }]; } // RACErrorSignal + (RACSignal *)error:(NSError *)error { RACErrorSignal *signal = [[self alloc] init]; signal->_error = error; return signal; } - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { return [RACScheduler.subscriptionScheduler schedule:^{ [subscriber sendError:self.error]; }]; }
这两个创建过程的唯一区别就是一个发送的是『空值』,另一个是
NSError对象。
信号的订阅与信息的发送
ReactiveCocoa 中信号的订阅与信息的发送过程主要是由 RACSubscriber类来处理的,而这也是信号的处理过程中最重要的一部分,这一小节会先分析整个工作流程,之后会深入代码的实现。
RACSignal-Subcribe-Process
在信号创建之后调用
-subscribeNext:方法返回一个
RACDisposable,然而这不是这一流程关心的重点,在订阅过程中生成了一个
RACSubscriber对象,向这个对象发送消息
-sendNext:时,就会向所有的订阅者发送消息。
信号的订阅
信号的订阅与 -subscribe:开头的一系列方法有关:
RACSignal-Subscribe-Methods
订阅者可以选择自己想要感兴趣的信息类型
next/error/completed进行关注,并在对应的信息发生时调用
block 进行处理回调。
所有的方法其实只是对
nextBlock、
completedBlock以及
errorBlock的组合,这里以其中最长的
-subscribeNext:error:completed:方法的实现为例(也只需要介绍这一个方法):
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock { RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock]; return [self subscribe:o]; }
方法中传入的所有 block 参数都应该是非空的。
拿到了传入的 block 之后,使用
+subscriberWithNext:error:completed:初始化一个
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; }
在拿到这个对象之后,调用
RACSignal的
-subscribe:方法传入订阅者对象:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { NSCAssert(NO, @"This method must be overridden by subclasses"); return nil; }
RACSignal类中其实并没有实现这个实例方法,需要在上文提到的四个子类对这个方法进行覆写,这里仅分析
RACDynamicSignal中的方法:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ RACDisposable *innerDisposable = self.didSubscribe(subscriber); [disposable addDisposable:innerDisposable]; }]; [disposable addDisposable:schedulingDisposable]; return disposable; }
这里暂时不需要关注与
RACDisposable有关的任何内容,我们会在下一节中详细介绍。
RACPassthroughSubscriber就像它的名字一样,只是对上面创建的订阅者对象进行简单的包装,将所有的消息转发给内部的
innerSubscriber,也就是传入的
RACSubscriber对象:
- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable { self = [super init]; _innerSubscriber = subscriber; _signal = signal; _disposable = disposable; [self.innerSubscriber didSubscribeWithDisposable:self.disposable]; return self; }
如果直接简化
-subscribe:方法的实现,你可以看到一个看起来极为敷衍的代码:
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { return self.didSubscribe(subscriber); }
方法只是执行了在创建信号时传入的
RACSignalBindBlock:
[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) { [subscriber sendNext:@1]; [subscriber sendNext:@2]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"dispose"); }]; }];
总而言之,信号的订阅过程就是初始化
RACSubscriber对象,然后执行
didSubscribe代码块的过程。
Principle-of-Subscribing-Signals
信息的发送
在 RACSignalBindBlock中,订阅者可以根据自己的兴趣选择自己想要订阅哪种消息;我们也可以按需发送三种消息:
RACSignal-Subcription-Messages-Sending
而现在只需要简单看一下这三个方法的实现,就能够明白信息的发送过程了(真是没啥好说的,不过为了凑字数完整性):
- (void)sendNext:(id)value { @synchronized (self) { void (^nextBlock)(id) = [self.next copy]; if (nextBlock == nil) return; nextBlock(value); } }
-sendNext:只是将方法传入的值传入
nextBlock再调用一次,并没有什么值得去分析的地方,而剩下的两个方法实现也差不多,会调用对应的
block,在这里就省略了。
订阅的回收过程
在创建信号时,我们向 -createSignal:方法中传入了
didSubscribe信号,这个
block 在执行结束时会返回一个
RACDisposable对象,用于在订阅结束时进行必要的清理,同样也可以用于取消因为订阅创建的正在执行的任务。
而处理这些事情的核心类就是
RACDisposable以及它的子类:
RACDisposable-And-Subclasses
这篇文章中主要关注的是左侧的三个子类,当然
RACDisposable的子类不止这三个,还有用于处理
KVO 的
RACKVOTrampoline,不过在这里我们不会讨论这个类的实现。
RACDisposable
在继续分析讨论订阅的回收过程之前,笔者想先对 RACDisposable进行简要的剖析和介绍:
RACDisposable
类
RACDisposable是以
_disposeBlock为核心进行组织的,几乎所有的方法以及属性其实都是对
_disposeBlock进行的操作。
关于 _disposeBlock 中的 self
这一小节的内容是可选的,跳过不影响整篇文章阅读的连贯性。_disposeBlock是一个私有的指针变量,当
void (^)(void)类型的 block 被传入之后都会转换成 CoreFoundation 中的类型并以
void *的形式存入
_disposeBlock中:
+ (instancetype)disposableWithBlock:(void (^)(void))block { return [[self alloc] initWithBlock:block]; } - (instancetype)initWithBlock:(void (^)(void))block { self = [super init]; _disposeBlock = (void *)CFBridgingRetain([block copy]); OSMemoryBarrier(); return self; }
奇怪的是,
_disposeBlock中不止会存储代码块 block,还有可能存储桥接之后的
self:
- (instancetype)init { self = [super init]; _disposeBlock = (__bridge void *)self; OSMemoryBarrier(); return self; }
这里,刚开始看到可能会觉得比较奇怪,有两个疑问需要解决:
为什么要提供一个
-init方法来初始化
RACDisposable对象?
为什么要向
_disposeBlock中传入当前对象?
对于
RACDisposable来说,虽然一个不包含
_disposeBlock的对象没什么太多的意义,但是对于
RACSerialDisposable等子类来说,却不完全是这样,因为
RACSerialDisposable在
-dispose时,并不需要执行
disposeBlock,这样就浪费了内存和
CPU 时间;但是同时我们需要一个合理的方法准确地判断当前对象的
isDisposed:
- (BOOL)isDisposed { return _disposeBlock == NULL; }
所以,使用向
_disposeBlock中传入
NULL的方式来判断
isDisposed;在
-init调用时传入
self而不是
NULL防止状态被误判,这样就在不引入其他实例变量、增加对象的设计复杂度的同时,解决了这两个问题。
如果仍然不理解上述的两个问题,在这里举一个错误的例子,如果
_disposeBlock在使用时只传入
NULL或者
block,那么在
RACCompoundDisposable初始化时,是应该向
_disposeBlock中传入什么呢?
传入
NULL会导致在初始化之后
isDisposed == YES,然而当前对象根本没有被回收;
传入
block会导致无用的
block 的执行,浪费内存以及 CPU 时间;
这也就是为什么要引入
self来作为
_disposeBlock内容的原因。
-dispose: 方法的实现
这个只有不到 20 行的 -dispose:方法已经是整个
RACDisposable类中最复杂的方法了:
- (void)dispose { void (^disposeBlock)(void) = NULL; while (YES) { void *blockPtr = _disposeBlock; if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) { if (blockPtr != (__bridge void *)self) { disposeBlock = CFBridgingRelease(blockPtr); } break; } } if (disposeBlock != nil) disposeBlock(); }
但是其实它的实现也没有复杂到哪里去,从
_disposeBlock实例变量中调用
CFBridgingRelease取出一个
disposeBlock,然后执行这个
block,整个方法就结束了。
RACSerialDisposable
RACSerialDisposable是一个用于持有
RACDisposable的容器,它一次只能持有一个
RACDisposable的实例,并可以原子地换出容器中保存的对象:
- (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable { RACDisposable *existingDisposable; BOOL alreadyDisposed; pthread_mutex_lock(&_mutex); alreadyDisposed = _disposed; if (!alreadyDisposed) { existingDisposable = _disposable; _disposable = newDisposable; } pthread_mutex_unlock(&_mutex); if (alreadyDisposed) { [newDisposable dispose]; return nil; } return existingDisposable; }
线程安全的
RACSerialDisposable使用
pthred_mutex_t互斥锁来保证在访问关键变量时不会出现线程竞争问题。
-dispose方法的处理也十分简单:
- (void)dispose { RACDisposable *existingDisposable; pthread_mutex_lock(&_mutex); if (!_disposed) { existingDisposable = _disposable; _disposed = YES; _disposable = nil; } pthread_mutex_unlock(&_mutex); [existingDisposable dispose]; }
使用锁保证线程安全,并在内部的
_disposable换出之后在执行
-dispose方法对订阅进行处理。
RACCompoundDisposable
与 RACSerialDisposable只负责一个
RACDisposable对象的释放不同;
RACCompoundDisposable同时负责多个
RACDisposable对象的释放。
相比于只管理一个
RACDisposable对象的
RACSerialDisposable,
RACCompoundDisposable由于管理多个对象,其实现更加复杂,而且为了性能和内存占用之间的权衡,其实现方式是通过持有两个实例变量:
@interface RACCompoundDisposable () { ... RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount]; CFMutableArrayRef _disposables; ... }
在对象持有的
RACDisposable不超过
RACCompoundDisposableInlineCount时,都会存储在
_inlineDisposables数组中,而更多的实例都会存储在
_disposables中:
RACCompoundDisposable
RACCompoundDisposable在使用
-initWithDisposables:初始化时,会初始化两个
RACDisposable的位置用于加速销毁订阅的过程,同时为了不浪费内存空间,在默认情况下只占用两个位置:
- (instancetype)initWithDisposables:(NSArray *)otherDisposables { self = [self init]; [otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) { self->_inlineDisposables[index] = disposable; if (index == RACCompoundDisposableInlineCount - 1) *stop = YES; }]; if (otherDisposables.count > RACCompoundDisposableInlineCount) { _disposables = RACCreateDisposablesArray(); CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount); CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range); } return self; }
如果传入的
otherDisposables多于
RACCompoundDisposableInlineCount,就会创建一个新的
CFMutableArrayRef引用,并将剩余的
RACDisposable全部传入这个数组中。
在
RACCompoundDisposable中另一个值得注意的方法就是
-addDisposable:
- (void)addDisposable:(RACDisposable *)disposable { if (disposable == nil || disposable.disposed) return; BOOL shouldDispose = NO; pthread_mutex_lock(&_mutex); { if (_disposed) { shouldDispose = YES; } else { for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { if (_inlineDisposables[i] == nil) { _inlineDisposables[i] = disposable; goto foundSlot; } } if (_disposables == NULL) _disposables = RACCreateDisposablesArray(); CFArrayAppendValue(_disposables, (__bridge void *)disposable); foundSlot:; } } pthread_mutex_unlock(&_mutex); if (shouldDispose) [disposable dispose]; }
在向
RACCompoundDisposable中添加新的
RACDisposable对象时,会先尝试在
_inlineDisposables数组中寻找空闲的位置,如果没有找到,就会加入到
_disposables中;但是,在添加
RACDisposable的过程中也难免遇到当前
RACCompoundDisposable已经
dispose的情况,而这时就会直接
-dispose刚刚加入的对象。
订阅的销毁过程
在了解了 ReactiveCocoa 中与订阅销毁相关的类,我们就可以继续对 -bind:方法的分析了,之前在分析该方法时省略了
-bind:在执行过程中是如何处理订阅的清理和销毁的,所以会省略对于正常值和错误的处理过程,首先来看一下简化后的代码:
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block { return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { RACSignalBindBlock bindingBlock = block(); __block volatile int32_t signalCount = 1; RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; void (^completeSignal)(RACDisposable *) = ... void (^addSignal)(RACSignal *) = ... RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; [compoundDisposable addDisposable:selfDisposable]; RACDisposable *bindingDisposable = [self subscribeNext:^(id x) { BOOL stop = NO; id signal = bindingBlock(x, &stop); if (signal != nil) addSignal(signal); if (signal == nil || stop) { [selfDisposable dispose]; completeSignal(selfDisposable); } } completed:^{ completeSignal(selfDisposable); }]; selfDisposable.disposable = bindingDisposable; return compoundDisposable; }] setNameWithFormat:@"[%@] -bind:", self.name]; }
在简化的代码中,订阅的清理是由一个
RACCompoundDisposable的实例负责的,向这个实例中添加
RACSerialDisposable以及
RACDisposable对象,并在
RACCompoundDisposable销毁时销毁。
completeSignal和
addSignal两个
block 主要负责处理新创建信号的清理工作:
void (^completeSignal)(RACDisposable *) = ^(RACDisposable *finishedDisposable) { if (OSAtomicDecrement32Barrier(&signalCount) == 0) { [subscriber sendCompleted]; [compoundDisposable dispose]; } else { [compoundDisposable removeDisposable:finishedDisposable]; } }; void (^addSignal)(RACSignal *) = ^(RACSignal *signal) { OSAtomicIncrement32Barrier(&signalCount); RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; [compoundDisposable addDisposable:selfDisposable]; RACDisposable *disposable = [signal completed:^{ completeSignal(selfDisposable); }]; selfDisposable.disposable = disposable; };
先通过一个例子来看一下
-bind:方法调用之后,订阅是如何被清理的:
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) { [subscriber sendNext:@1]; [subscriber sendNext:@2]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"Original Signal Dispose."); }]; }]; RACSignal *bindSignal = [signal bind:^RACSignalBindBlock _Nonnull{ return ^(NSNumber *value, BOOL *stop) { NSNumber *returnValue = @(value.integerValue); return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) { for (NSInteger i = 0; i < value.integerValue; i++) [subscriber sendNext:returnValue]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"Binding Signal Dispose."); }]; }]; }; }]; [bindSignal subscribeNext:^(id _Nullable x) { NSLog(@"%@", x); }];
在每个订阅创建以及所有的值发送之后,订阅就会被就地销毁,调用
disposeBlock,并从
RACCompoundDisposable实例中移除:
1 Binding Signal Dispose. 2 2 Binding Signal Dispose. Original Signal Dispose.
原订阅的销毁时间以及绑定信号的控制是由
SignalCount控制的,其表示
RACCompoundDisposable中的
RACSerialDisposable实例的个数,在每次有新的订阅被创建时都会向
RACCompoundDisposable加入一个新的
RACSerialDisposable,并在订阅发送结束时从数组中移除,整个过程用图示来表示比较清晰:
RACSignal-Bind-Disposable
紫色的
RACSerialDisposable为原订阅创建的对象,灰色的为新信号订阅的对象。
总结
这是整个 ReactiveCocoa 源代码分析系列文章的第一篇,想写一个跟这个系列有关的代码已经很久了,文章中对于 RACSignal进行了一些简单的介绍,项目中绝大多数的方法都是很简洁的,行数并不多,代码的组织方式也很易于理解。虽然没有太多让人意外的东西,不过整个工程还是很值得阅读的。
References
A Fistful of MonadsWhat
is (functional) reactive programming?
方法实现对照表
方法 | 实现 |
---|---|
+return: | RACSignal.m#L89-L91 |
-bind: | RACSignal.m#L93-176 |
Follow: Draveness · GitHub
Source: http://draveness.me/racsignal
转自:http://www.jianshu.com/p/4e14ee63a12b
相关文章推荐
- ReactiveCocoa基本组件:理解和使用RACCommand
- ReactiveCocoa要点:理解和使用RACCommand
- ReactiveCocoa基本组件:理解和使用RACCommand
- reactivecocoa框架 、MVC 和 MVVM 的理解
- ReactiveCocoa之理解sample信号
- ReactiveCocoa基本组件:理解和使用RACCommand
- ReactiveCocoa 基本使用回忆录
- 最快让你上手ReactiveCocoa之基础篇
- ReactiveCocoa 函数响应式编程简介 链式编程 函数式编程 响应式编程
- ReactiveCocoa基本使用
- iOS响应式编程:ReactiveCocoa vs RxSwift 选谁好
- ReactiveCocoa -- Framework Overview
- ReactiveCocoa & MVVM 学习总结一
- MVVM With ReactiveCocoa
- MVVM With ReactiveCocoa
- ReactiveCocoa Weak-Strong Dance
- 一次MVVM+ReactiveCocoa实践
- MVVM With ReactiveCocoa
- ReactiveCocoa常见用法练习
- MVVM With ReactiveCocoa