ReactiveCocoa源码精讲(一) -- RACSignal

ReactiveCocoa

ReactiveCocoa(简称RAC)是由GitHub团队开源的一套基于Cocoa的并且具有FRP特性的框架。FRP(Functional Reactive Programming)即响应式编程。RAC就是一个第三方库,使用它可以大大简化代码,提高开发效率,目前公司也在范围使用。但疏于总结只是停留在会用的阶段,这次针对RAC做个全面认识和总结。 第一部分基础理论。 第二部分介绍一些常用类。 第三部分介绍一些常用语法。

RACSignal使用代码

	//1.创建信号
    RACSignal *singal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        //3.发送信号
        [subscriber sendNext:@"1"];
        [subscriber sendCompleted];
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"RACDisposable...");
        }];
    }];
    
    //2.订阅信号
    [singal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

使用RacSignal需要三个步骤:
1.创建信号

	[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {}];

2.订阅信号

	[singal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

3.发送信号

		//3.发送信号
        [subscriber sendNext:@"1"];
        [subscriber sendCompleted];
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"RACDisposable...");
        }];

RacSingal源码分析

RACSignal 创建信号

	[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {}];

首先,进入createSignal我们可以看到内部声明的类方法用来创建一个RACSignal类。

	+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
		return [RACDynamicSignal createSignal:didSubscribe];
	}

观察Block块,我们不难发现需要Block返回值为RACDisposable,参数为subscriber订阅者。
在利用createSignal创建的信号开始为冷信号,同时在block块中,我们需要返回值

	return nil; 

或者

	return [RACDisposable disposableWithBlock:^{
	            NSLog(@"RACDisposable...");
	        }];

来生成RACDisposable。
我们看到createSignal中并没有直接进行创建操作,而是调用了RACDynamicSignal这个类进行创建信号。
我们继续深入看RACDynamicSignal 如何生成冷信号。

RACDynamicSignal 创建信号

	+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
		RACDynamicSignal *signal = [[self alloc] init];
		signal->_didSubscribe = [didSubscribe copy];
		return [signal setNameWithFormat:@"+createSignal:"];
	}

1.先进行了alloc init来创建RACDynamicSignal的对象signal;
2.通过将传入的didSubscribe拷贝给signal自己的didSubscribe;
3.返回当前对象 – 为当前对象设置一个名称标签

观察init方法

	- (instancetype)init {
		self = [super init];
		if (self == nil) return nil;
		
		// As soon as we're created we're already trying to be released. Such is life.
		[self invalidateGlobalRefIfNoNewSubscribersShowUp];
		
		return self;
}
	- (void)invalidateGlobalRefIfNoNewSubscribersShowUp {
		// If no one subscribes in one pass of the main run loop, then we're free to
		// go. It's up to the caller to keep us alive if they still want us.
		RACSignalList *elem = malloc(sizeof(*elem));
	
		// This also serves to retain the signal until the next pass.
		elem->retainedSignal = CFBridgingRetain(self);
		OSAtomicEnqueue(&RACActiveSignalsToCheck, elem, offsetof(RACSignalList, next));
	
		// Not using a barrier because duplicate scheduling isn't erroneous, just
		// less optimized.
		int32_t willCheck = OSAtomicOr32Orig(1, &RACWillCheckActiveSignals);
	
		// Only schedule a check if RACWillCheckActiveSignals was 0 before.
		if (willCheck == 0) {
			dispatch_async(dispatch_get_main_queue(), ^{
				RACCheckActiveSignals();
			});
		}
	}

1.RACSignalList – 结构体

	typedef struct RACSignalList {
		CFTypeRef retainedSignal;
		struct RACSignalList * restrict next;
	} RACSignalList;

官方给出的解释是
A linked list of RACDynamicSignals, used in RACActiveSignalsToCheck.
(RACActiveSignalsToCheck中使用的RACDynamicSignals的链接列表)

通过将self(也就是自身的RACDynamicSignal)转换成为CF类型(在源码中多次用到了CFBridgingRetain()函数),赋值给elem结构体对象的保留信号。
实现自动入队

	__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_4_0)
	void  OSAtomicEnqueue( OSQueueHead *__list, void *__new, size_t __offset);

观察传入的参数分别是

	static OSQueueHead RACActiveSignalsToCheck = OS_ATOMIC_QUEUE_INIT;
	elem
	offsetof(RACSignalList, next)

思考:为什么设计成为静态???
个人认为,设计成为静态是为了在程序运行时候,会多次创建信号,创建信号就会调用到init方法,从而设计成为static变成静态是为了只加载一次内存,从而防止出现内存重复加载问题。

这个方法,核心做的事情是将信号保存到一个保留信号列表中,为后期响应式编程提供了有力的条件。

subscribeNext 订阅信号

一旦订阅了信号,此信号就从冷信号变成了热信号。

思考:我们是先发送还是先订阅???
个人认为,在RACSignal中,我们应该先订阅后发送,这是因为,如果我们先发送,那么订阅就变的毫无意义,但是,总有特别的案例,后期,我们会单独讲解ReactiveCocoa提供的先订阅后发送的类。

订阅信号,通过subscribeNext来实现

	[singal subscribeNext:^(id x) {
	        NSLog(@"%@",x);
	    }];

同样,我们进入内部来观察它具体做了什么操作

	- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
		NSCParameterAssert(nextBlock != NULL);
		
		RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
		return [self subscribe:o];
	}

我们来看核心部分,还是一样,它并没有直接进行订阅,而是调用了RACSubscriber来订阅信号,传入参数,我们把需要订阅的信号传入。

	#import "RACSubscriber.h"
	
	/// A simple block-based subscriber.
	@interface RACSubscriber : NSObject <RACSubscriber>
	
	/// Creates a new subscriber with the given blocks.
	+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed;

源码提供这个订阅者类十分简介,只有一个对外的类方法,用于创建对象。
我们看到,这个类继承自NSObject,其约束于RACSubscriber

	subscriber->_next = [next copy];
	subscriber->_error = [error copy];
	subscriber->_completed = [completed copy];

内部操作,也十分惹人喜欢,只是将对应参数进行了保存。
做完这些操作后,RACSingal订阅这个订阅者即可。

	return [self subscribe:o];

千万不要被迷惑,订阅者传入的block参数id x此时作为next已经被订阅者所管理。等待的就是被调用。

subscribe 订阅订阅者

	- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
	NSCParameterAssert(subscriber != nil);

	RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
	subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

	OSSpinLockLock(&_subscribersLock);
	if (_subscribers == nil) {
		_subscribers = [NSMutableArray arrayWithObject:subscriber];
	} else {
		[_subscribers addObject:subscriber];
	}
	OSSpinLockUnlock(&_subscribersLock);
	
	@weakify(self);
	RACDisposable *defaultDisposable = [RACDisposable disposableWithBlock:^{
		@strongify(self);
		if (self == nil) return;

		BOOL stillHasSubscribers = YES;

		OSSpinLockLock(&_subscribersLock);
		{
			// 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];
				stillHasSubscribers = _subscribers.count > 0;
			}
		}
		OSSpinLockUnlock(&_subscribersLock);
		
		if (!stillHasSubscribers) {
			[self invalidateGlobalRefIfNoNewSubscribersShowUp];
		}
	}];

	[disposable addDisposable:defaultDisposable];

	if (self.didSubscribe != NULL) {
		RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
			RACDisposable *innerDisposable = self.didSubscribe(subscriber);
			[disposable addDisposable:innerDisposable];
		}];

		[disposable addDisposable:schedulingDisposable];
	}
	
	return disposable;
}

这个函数看起来很可怕,不过这是最核心的部分,订阅订阅者。我们静下心来分析核心部分。

sendNext 发送信号

发送信号,是通过RACSubsriber来发送的。这个就验证了之前先订阅(通过RACSubsriber,从而保存订阅的next),在发送

	- (void)sendNext:(id)value {
	    /* 发送信号可能会导致出现多个发送信号同时发送,出现交叉的情况。因此,需要使用同步锁 */
		@synchronized (self) {
			void (^nextBlock)(id) = [self.next copy];
			if (nextBlock == nil) return;
	
			nextBlock(value);
		}
	}

发送信号,传入的参数是id类型。
思考:为什么使用同步锁机制???
个人认为,发送信号可能会导致出现多个发送信号同时发送,出现交叉的情况。因此,需要使用同步锁。

观察核心代码
源码先声明了一个名称为nextBlock的Block块用于存储自身next的值,这个值在之前有保存,nextBlock为空,则什么都不做,直接返回。否则记录当前值。
调用sendCompleted实现发送完成操作

	[subscriber sendCompleted];

实现图RACSignal

思路总结:

使用RACSignal实际上内部做了这几个操作

  1. 利用RACDynamicSignal创建冷信号;
  2. 通过RACSubscriber订阅信号,并将定于的信号保存在RACSubscriber内部的next中;
  3. 订阅信号订阅订阅者subscriber;
  4. 通过RACSubscriber发送信号;

分析源码下载

小编已经将阅读批注的源码放到了Github上,需要的小伙伴可以下载阅读,如果有哪里不正确或者有什么问题,可以一起评论留言分享自己的学习成果。
链接: Github ReativeCocod.

发布了5 篇原创文章 · 获赞 0 · 访问量 36

猜你喜欢

转载自blog.csdn.net/weixin_39647415/article/details/105504186
今日推荐