ReactiveObjC 学习笔记

本系列从 RACSignal 出发,遇到坑现踩现填,想到什么写什么。有写的不对的地方,欢迎指正。

RACSignal

通常使用这个方法?来初始化一个 Signal

+ (RACSignal<ValueType> *)createSignal:(RACDisposable * _Nullable (^)(id<RACSubscriber> subscriber))didSubscribe;
复制代码

但它实际上返回的是一个 RACSignal 的子类 RACDynamicSignal。其中 didSubscribe 参数是一个 block,每当信号被订阅的时候,这个 block 都会被调用。?

// RACSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
	// 实则创建的是 RACDynamicSignal
	return [RACDynamicSignal createSignal:didSubscribe];
}
复制代码
// RACDynamicSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
	RACDynamicSignal *signal = [[self alloc] init];
	// 保存一份 block 的 copy
	signal->_didSubscribe = [didSubscribe copy];
	return [signal setNameWithFormat:@"+createSignal:"];
}
复制代码

再来看这个 block 的类型?

RACDisposable * _Nullable (^)(id<RACSubscriber> subscriber)
复制代码

它会被传入一个 id<RACSubscriber> 类型的参数,你可以通过调用这个 subscriber-sendNext:-sendError:-sendCompleted 方法来手动触发你传给 -subscribeNext:-subcribeError:-subscribeCompleted: 的 block 。 除此之外,这个 block 会返回一个 RACDisposable 类型的实例,用于操作即将被销毁的信号。在 RACDisposable-dispose 方法被调用后,Signal 将无法再向 Subscriber 发送事件。

通过这个方法?来获得一个立即执行 block 的 Signal

+ (RACSignal<ValueType> *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block;
复制代码

它的实现实际上调用了另一个方法?

+ (RACSignal<ValueType> *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block;
复制代码

也就是获取一个“惰性的” Signal,他的 block 只有在被首次订阅的时候才会调用,但是会为新 Subscriber 重播它错过的事件。 比如下面这个 Demo?

RACSignal *signalLazy = [RACSignal startLazilyWithScheduler:[RACScheduler mainThreadScheduler] block:^(id<RACSubscriber>  _Nonnull subscriber) {
        NSLog(@"signalLazy");
        [subscriber sendNext:@"a"];
        [subscriber sendNext:@"b"];
        [subscriber sendNext:@"c"];
        [subscriber sendCompleted];
}];
// 首次订阅
[signalLazy subscribeNext:^(NSString * _Nullable x) {
	NSLog(@"lazySignal: %@", x);
}];
// 再次订阅
[signalLazy subscribeNext:^(NSString * _Nullable x) {
	NSLog(@"lazySignal: %@", x);
}];
// output:
// signalLazy
// lazySignal: a
// lazySignal: b
// lazySignal: c
// lazySignal: a
// lazySignal: b
// lazySignal: c
复制代码

从上面的 Demo 可以看出,即使被订阅了两次,但是 Lazy Signal 的 block 只被调用了一次,而且它还贴心地为第二个 Subscriber 重播了之前错过的事件。 我们来看一下它的实现?

+ (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block {
	NSCParameterAssert(scheduler != nil);
	NSCParameterAssert(block != NULL);
	// 创建一个 RACMulticastConnection,用于在源 Signal 与 RACReplaySubject 之间建立连接
	RACMulticastConnection *connection = [[RACSignal
		createSignal:^ id (id<RACSubscriber> subscriber) {
			// 直接调用 block,且不返回 Disposable
			block(subscriber);
			return nil;
		}]
		multicast:[RACReplaySubject subject]];
	// 返回一个 Signal
	return [[[RACSignal
		createSignal:^ id (id<RACSubscriber> subscriber) {
			// 这里的 connection.signal 就是上面的? RACReplaySubject
			// 所以实际上新 subscriber 订阅的是 RACReplaySubject
			[connection.signal subscribe:subscriber];
			// 建立源 Signal 与 RACReplaySubject 之间的连接
			// 本质就是订阅源 Signal,但只能订阅一次
			[connection connect];
			return nil;
		}]
		subscribeOn:scheduler]
		setNameWithFormat:@"+startLazilyWithScheduler: %@ block:", scheduler];
}
复制代码

上面的实现涉及到了两个核心类:

  • RACMulticastConnection:用于实现一对多的事件转发。比如 B 订阅了 A ,同时又有 C、D 也想订阅 A。这个时候如果有一个 E 来专门负责从 A 到 所有 Subscriber 的转发,就会方便很多,所有 Subscriber 只需要订阅 E 就可以了,不需要关心 A 。

  • RACReplaySubject:顾名思义,用于实现对事件的重播。这个类可以记录自己所发出的所有事件,在有新的 Subscriber 订阅时,会把所有事件都重播给这个新 Subscriber 。

实现的重点在于,Subscriber 订阅的并不是源 Signal 本身,而是有重播功能的 RACReplaySubject ,当有订阅时才去调用 -connect ,所以实现了“惰性”。那么问题来了:明明订阅的是 RACReplaySubject 为什么还会走一次源 Signal 的 block?关于这个问题,其实从上面的注释已经给出了简短的解答。

// 这里的 connection.signal 就是上面的? RACReplaySubject
// 所以实际上新 subscriber 订阅的是 RACReplaySubject
[connection.signal subscribe:subscriber];
// 建立源 Signal 与 RACReplaySubject 之间的连接
// 本质就是订阅源 Signal,但只能订阅一次
[connection connect];
复制代码

每当有新的 Subscriber 订阅这个 Signal 时,会先将 Subscriber 传给 connection.signal 也就是 RACReplaySubject ,这会使 connection.signal 开始重播已有的事件;随后调用 -connect 也就是让 connection.signal 订阅 sourceSignal ,见 -connect 方法的实现?

- (RACDisposable *)connect {
	BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
	// 如果已经连接,则不会再订阅
	// 也就是为什么源 Signal 的 block 只会调用一次
	if (shouldConnect) {
		// _signal 在内部实际上是一个 RACSubject
		self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
	}

	return self.serialDisposable;
}
复制代码

由此可见,当第一个 Subscriber 订阅时,由于 _signal 还没有记录过任何事件,所以 [connection.signal subscribe:subscriber] 没有发出任何事件。但由于是第一次 -connect ,所以会调用源 Signal 的 block 并发出相应的事件给 connection.signal 来记录。到了后续的 Subscriber 订阅时,connection.signal 会重播源 Signal 的事件,并且由于已经连接, -connect 不会再生效了,所以不会再调用源 Signal 的 block 。

注意startLazilyWithScheduler:block: 方法返回的 Signal ,即使外部 Subscriber 对它进行了 dispose ,也不会触发其内部的 Subject 与 源 Signal 之间的 dispose。

看完了 +startLazilyWithScheduler:block: ,再回过头来看 +startEagerlyWithScheduler:block: ,之前提到它是由 +startLazilyWithScheduler:block: 实现的

+ (RACSignal *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block {
	NSCParameterAssert(scheduler != nil);
	NSCParameterAssert(block != NULL);

	RACSignal *signal = [self startLazilyWithScheduler:scheduler block:block];
	// Subscribe to force the lazy signal to call its block.
	[[signal publish] connect];
	return [signal setNameWithFormat:@"+startEagerlyWithScheduler: %@ block:", scheduler];
}

- (RACMulticastConnection *)publish {
	RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
	RACMulticastConnection *connection = [self multicast:subject];
	return connection;
}
复制代码

可以看出,“勤奋” 只是它的表象罢了,其骨子里还是个“死肥宅”。在它被创建的那一刻,就被手动与一个默认的 RACSubject 建立了连接,从而实现了立即订阅。

RACMulticastConnection

关于这个类的作用,上文已经提到:实现了一对多的事件转发。它的方法也很少,只有两个:

- (RACDisposable *)connect;
- (RACSignal<ValueType> *)autoconnect;
复制代码

-connect 已经讲过,下面我们来看 -autoconnect ,看头文件作者的注释,基本已可以了解它的功能:

Connects to the underlying signal when the returned signal is first subscribed to, and disposes of the subscription to the multicasted signal when the returned signal has no subscribers. If new subscribers show up after being disposed, they'll subscribe and then be immediately disposed of. The returned signal will never re-connect to the multicasted signal.

可见 -autoconnect 不仅具备只有首次连接才会触发订阅的逻辑,还可以在外部 Subscriber 数量为 0 的情况下自动 dispose 内部源 Signal 与 Subject 之间的订阅关系。而且一旦断开,就不会再重连。

来看一下它的实现?

- (RACSignal *)autoconnect {
	__block volatile int32_t subscriberCount = 0;

	return [[RACSignal
		createSignal:^(id<RACSubscriber> subscriber) {
			// 每当被订阅,计数器++
			OSAtomicIncrement32Barrier(&subscriberCount);
			// Subject 的 Disposable
			RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];
			// 源 Signal 的 Disposable
			RACDisposable *connectionDisposable = [self connect];
			// 实现自身的 Disposable
			return [RACDisposable disposableWithBlock:^{
				// dispose Subject 的 Disposable
				// 也就是断开外部 Subscriber 与 Subject 之间的订阅
				[subscriptionDisposable dispose];
				// 每当 dispose ,计数器--
				if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {
					// 当外部 Subscriber 为 0 时
					// dispose 源 Signal 的 Disposable
					// 也就是断开 Subject 与源 Signal 之间的订阅
					[connectionDisposable dispose];
				}
			}];
		}]
		setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值