ReactiveCocoa源码精讲(五) -- 终结篇

ReactiveCocoa终结篇

前面四篇文章我们分析了RAC的常用的基本用法和源码实现。今天,是关于RAC源码分析的最后一篇文章。今天介绍的两个类是RACMulticastConnection和RACCommand,这两个使用在现实开发中有着至关重要的作用,尤其是网络请求案例。

RACMulticastConnection

使用方法:

//1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"热门模块的数据"];
        
        return nil;
    }];
    
    //2.把信号转换为连接类
    //    RACMulticastConnection *connection = [signal publish];
    RACMulticastConnection *connection = [signal multicast:[RACReplaySubject subject]];
    
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"订阅者1:%@",x);
    }];

    [connection.signal subscribeNext:^(id x) {
        NSLog(@"订阅者2:%@",x);
    }];

    [connection connect];

对于这个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.(多播连接封装了将一个订阅信号共享给许多订阅者的想法。如果对基础信号的订阅涉及副作用或不应被多次调用,则最常需要此方法。)

我们可以看出来跟名称相同MulticastConnection实现了多播连接。

思考:为什么设计一个多播连接,难道之前的信号不好吗?
个人根据官方定义认为,如果有一种需求,当一个信号需要被多个订阅者订阅时候,需要多次被调用,这样对于资源有一定的消耗,因此,采用多播连接调用,可以省去额外的资源开销从而使得效率更高。

了解完概念后,我们来看源码是如何实现的。

RACMulticastConnection 创建连接

通过一个信号来调用,从而使得这个信号可以被多个订阅者调用。

- (RACMulticastConnection *)multicast:(RACSubject *)subject {
	[subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
	RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
	return connection;
}
- (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 = subject;
	
	return self;
}

核心代码,做了三个赋值操作,分别是对原信号的赋值,回收机制的赋值,信号的赋值。

  1. _sourceSignal: RACMulticastConnection自身就是一个信号,一个源信号;
  2. _signal:表示接受一个订阅者信号
    当然,你可以使用下面这种方式来创建多播连接,
RACMulticastConnection *connection = [signal publish];
- (RACMulticastConnection *)publish {
	RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
	RACMulticastConnection *connection = [self multicast:subject];
	return connection;
}

底层还是调用上面的方法进行创建多播,不过是做了二次封装而已。
看到这里,大家一定有一个疑问,为什么要传入一个订阅者呢???

RACMulticastConnection 转为信号订阅信号

这里,将connection转换成为信号,在通过信号的RACSignal提供的方法进行订阅信号

   [connection.signal subscribeNext:^(id x) {
        NSLog(@"订阅者1:%@",x);
    }];
   	
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"订阅者2:%@",x);
    }];
    
RACMulticastConnection 连接

使用时候,一定不要忘记连接着一个操作。

[connection connect];
- (RACDisposable *)connect {
	BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);

	if (shouldConnect) {
        /* 订阅 */
		self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
	}

	return self.serialDisposable;
}

这里面我们需要分析一下,首先,判断是否可连接,通过OSAtomicCompareAndSwap32Barrier来比较和交换32位二进制数据,如果之前的hasConnected是没有连接的,也就是说0和&_hasConnected是相同值时候,我们交换1给_hasConnected,让其变成可连接。

当交换完成变成可连接时候,我们开始了订阅信号操作,订阅的信号就解决了我们之前的疑惑,之所以传入一个订阅者,是为了在连接时候进行更好的订阅信号。

自己充当源信号订阅信号。

我们之前分析过,RACReplaySubject可以用数组存储需要订阅的信号,因此这里面订阅了subject当连接成功时候,会被一次性全部调用,并且通过subscribeNext显示输出。

还有一个主角RACCommand

RACCommand

RACCommand看起来更加憨态可掬,因为,它创建的时候,内部是含有信号的,也就是内部信号。

RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"请求数据"];
            [subscriber sendCompleted];
            return nil;
        }];
    }];
    self.command = command;
    
    [command.executionSignals subscribeNext:^(id x) {
        [x subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
    }];
    [self.command execute:@1];

如果你看它的创建方法,你一定会吓疯的,这么长,我该从何入手。下面我们分解这个源码。

- (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];
	_signalBlock = [signalBlock copy];

	// A signal of additions to `activeExecutionSignals`.
	RACSignal *newActiveExecutionSignals = [[[[[self
		rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil]
		reduceEach:^(id _, NSDictionary *change) {
			NSArray *signals = change[NSKeyValueChangeNewKey];
			if (signals == nil) return [RACSignal empty];

			return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler];
		}]
		concat]
		publish]
		autoconnect];

    /* 将newActiveExecutionSignals转换成为可执行信号 */
	_executionSignals = [[[newActiveExecutionSignals
		map:^(RACSignal *signal) {
			return [signal catchTo:[RACSignal empty]];
		}]
		deliverOn:RACScheduler.mainThreadScheduler]
		setNameWithFormat:@"%@ -executionSignals", self];
	
	// `errors` needs to be multicasted so that it picks up all
	// `activeExecutionSignals` that are added.
	//
	// In other words, if someone subscribes to `errors` _after_ an execution
	// has started, it should still receive any error from that execution.
    /* 调用多播用法 */
	RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals
		flattenMap:^(RACSignal *signal) {
			return [[signal
				ignoreValues]
				catch:^(NSError *error) {
					return [RACSignal return:error];
				}];
		}]
		deliverOn:RACScheduler.mainThreadScheduler]
		publish];
	
	_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
	[errorsConnection connect];

	RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) {
		return @(activeSignals.count > 0);
	}];

	_executing = [[[[[immediateExecuting
		deliverOn:RACScheduler.mainThreadScheduler]
		// This is useful before the first value arrives on the main thread.
		startWith:@NO]
		distinctUntilChanged]
		replayLast]
		setNameWithFormat:@"%@ -executing", self];

	RACSignal *moreExecutionsAllowed = [RACSignal
		if:RACObserve(self, allowsConcurrentExecution)
		then:[RACSignal return:@YES]
		else:[immediateExecuting not]];
	
	if (enabledSignal == nil) {
		enabledSignal = [RACSignal return:@YES];
	} else {
		enabledSignal = [[[enabledSignal
			startWith:@YES]
			takeUntil:self.rac_willDeallocSignal]
			replayLast];
	}
	
	_immediateEnabled = [[RACSignal
		combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
		and];
	
	_enabled = [[[[[self.immediateEnabled
		take:1]
		concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
		distinctUntilChanged]
		replayLast]
		setNameWithFormat:@"%@ -enabled", self];

	return self;
}

第一板块:

	_activeExecutionSignals = [[NSMutableArray alloc] init];
	_signalBlock = [signalBlock copy];

这里面创建了一个活动的信号,和一个信号的block回调,我们斗胆猜测,这里后面可能会判断RACCommand中的内部信号是否是有效活跃的。

第二板块:

// A signal of additions to `activeExecutionSignals`.
	RACSignal *newActiveExecutionSignals = [[[[[self
		rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil]
		reduceEach:^(id _, NSDictionary *change) {
			NSArray *signals = change[NSKeyValueChangeNewKey];
			if (signals == nil) return [RACSignal empty];

			return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler];
		}]
		concat]
		publish]
		autoconnect];

我们通过之前创建的活跃信号来创建一个RACSignal的信号,并且转换成为可执行信号。

第三板块:

_executionSignals = [[[newActiveExecutionSignals
		map:^(RACSignal *signal) {
			return [signal catchTo:[RACSignal empty]];
		}]
		deliverOn:RACScheduler.mainThreadScheduler]
		setNameWithFormat:@"%@ -executionSignals", self];

第四板块:

RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals
		flattenMap:^(RACSignal *signal) {
			return [[signal
				ignoreValues]
				catch:^(NSError *error) {
					return [RACSignal return:error];
				}];
		}]
		deliverOn:RACScheduler.mainThreadScheduler]
		publish];
		_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
	[errorsConnection connect];

使用多播连接创建错误连接,并订阅。

看到这里,大致猜测一下它初始化时候做了保存传入的内部信号,并且检测其活跃性和可执行性的同时发送这个内部信号,如果有错误则利用多播连接进行发送,到此为止,我们知道这么多就够了。

[command.executionSignals subscribeNext:^(id x) {
        [x subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
    }];
[self.command execute:@1];

我们继续将RACCommand转换成为一个可执行信号,然后订阅RACCommand信号,此时的x是内部信号,我们要得到内部信号发送的信号,就要在使用一个订阅信号,进行订阅其内部的信号,这是多么神奇的设计模式。
当[self.command execute:@1];时候,command变成可执行,就会收到订阅的消息。

同时注意,为了保证command持久有效,我们需要使用strong类型强引用command,防止被回收后无法成为可执行信号的悲惨下场。

至此,我们分析了关于ReactiveCocoa一些基础用法和源码分析,思路是最重要的,不一定需要看懂每一句源码,但是大致了解内部是如何设计和为什么这样设计才是最重要的思想。
对于RAC的高级用法,小编暂时还没有进行深入的研读,如果,对与宏定义有所理解,会专门开辟一个专题分析RAC宏的高级用法。

源码下载

中文解析源码: GitHub.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值