本文是来自@厦大的投稿
ReactiveCocoa(简称RAC)是由GitHub团队开源的一套基于Cocoa的并且具有FRP特性的框架。FRP(Functional Reactive Programming)即响应式编程。RAC就是一个第三方库,使用它可以大大简化代码,提高开发效率,目前公司也在范围使用。但疏于总结只是停留在会用的阶段,这次针对RAC做个全面认识和总结。 第一部分基础理论。 第二部分介绍一些常用类。 第三部分介绍一些常用语法。
RAC的核心就是信号,即RACSignal。信号可以看做是传递数据的工具,当数据变化时,信号就会发送改变的信息,以通知信号的订阅者执行方法。
1.Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。 2.Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。 3.RACSubject及其子类是热信号。RACSignal排除RACSubject类以外的是冷信号。 推荐美团的技术博客介绍详细的冷热信号,在这里也要感谢美团技术团队博客带来的帮助。 冷信号与热信号(一) 为什么要区分冷热信(二) 怎么处理冷信号与热信号(三)
信号类,只有当数据变化时,才会发送数据,但是RACSignal自己不具备发送信号能力,而是交给订阅者去发送。默认一个信号发送数据完毕就会自动取消订阅,如果订阅者还在,就不会自动取消信号订阅,因此如果实际开发中需要自己控制订阅者的声明周期,可以stong持有,在特定的时机执行dispose方法取消订阅。 RACSignal订阅和发送信号一般过程如下: 1>创建信号 createSignal
RACSignal *single = [RACSignal createSignal:^RACDisposable *(idsubscriber) { }];
2>创建订阅者进行订阅
[single subscribeNext:]
3发送信号
[subscriber sendNext:]
RACSignal工作原理: 第一步 查看信号的创建过程: 当我们调用createSignal方法的时候,内部会调用子类RACDynamicSignal的createSignal方法创建一个信号single,并且在single中保存了block参数didSubscribe。
RACSignal.m: + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { return [ RACDynamicSignal createSignal :didSubscribe]; } RACDynamicSignal.m + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { RACDynamicSignal *signal = [[ self alloc ] init ]; signal-> _didSubscribe = [didSubscribe copy ]; return [signal setNameWithFormat : @"+createSignal:" ]; }
第二步 查看信号订阅的过程: 当我们调用信号的subscribeNext方法,内部创建一个订阅者subscriber,并且会保存参数nextBlock,还有errorBlock、completedBlock。接下来会调用RACDynamicSignal的subscribe方法,之前保存的didSubscribe,因为RACDynamicSignal是RACSignal的子类,所以会执行到这里。
RACSignal.m: - ( RACDisposable *)subscribeNext:( void (^)( id x))nextBlock { RACSubscriber *o = [ RACSubscriber subscriberWithNext :nextBlock error : NULL completed : NULL ]; return [ self subscribe:o]; } RACSubscriber.m: + ( 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; } RACDynamicSignal.m: - (RACDisposable *)subscribe:(id)subscriber { RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; if (self.didSubscribe != NULL) { RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ RACDisposable *innerDisposable = self.didSubscribe(subscriber); [disposable addDisposable:innerDisposable]; }]; [disposable addDisposable:schedulingDisposable]; } return disposable; }
第三步 查看发送信号的过程: 当执行订阅者的sendNext方法时,就会执行之前创建订阅者保存的那个nextBlock方法。
RACSubscriber.m: - (void)sendNext:(id)value { @synchronized (self) { void (^nextBlock)(id) = [self.next copy]; if (nextBlock == nil) return; nextBlock(value); } }
订阅者,它不是一个类而是一个协议,实现了这个协议的类都称为订阅者。
执行订阅取消或者进行对资源的清理工作,dispose。
是一个继承RACSignal并且遵守RACSubscriber协议的类。所以这一个类不仅可以处理信号,还可以发送信号。因为RACSubject的subscribeNext方法内部有数组subscribers,可以保存所有的订阅者,而RACSubject的sendNext再发送信号的时候会遍历所有的订阅者,订阅者执行nextBlock。下面是RACSubject信号实现的具体细节:
- (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]; } 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 (idobj, NSUInteger index, BOOL *stop) { return obj == subscriber; }]; if (index != NSNotFound) [subscribers removeObjectAtIndex:index]; } }]; } - (void)sendNext:(id)value { [self enumerateSubscribersUsingBlock:^(idsubscriber) { [subscriber sendNext:value]; }]; }
继承RACSubject,和RACSubject不同之处在于RACReplaySubject可以先发送信号,然后再订阅信号。原因在于RACReplaySubject的subscribe方法中遍历所有的订阅者,拿到当前订阅者发送数据。RACReplaySubject的sendNext方法是先保存值,然后再发送数据,RACSubject则是直接遍历发送数据。
RACReplaySubject.m - (RACDisposable *)subscribe:(id)subscriber { RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ @synchronized (self) { for (id value in self.valuesReceived) { if (compoundDisposable.disposed) return; [subscriber sendNext:([value isKindOfClass:RACTupleNil.class] ? nil : value)];//订阅者发送数据 } if (compoundDisposable.disposed) return; if (self.hasCompleted) { [subscriber sendCompleted]; } else if (self.hasError) { [subscriber sendError:self.error]; } else { RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; [compoundDisposable addDisposable:subscriptionDisposable] } } }]; [compoundDisposable addDisposable:schedulingDisposable]; return compoundDisposable; } - (void)sendNext:(id)value { @synchronized (self) { [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];//先保存值 [super sendNext:value]; //再发送数据 if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) { [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)]; } } }
连接类是为了当我们多次订阅同一个信号的时候,避免订阅信号的block中的代码被调用多次。 具体用法见例子7
这个类是负责处理事件的类,可以控制事件的传递以及数据的传递,监控事件的执行过程。
1.1> 监听对象的属性变化
[RACObserve(self.scrollView, contentSize) subscribeNext:^(id x) { }];
1.2> 监听Bool值改变
[RACObserve(self, bCheck) subscribeNext:^(id x) { }];
1.3> 监听方法
监听某个方法被调用会触发 [[self rac_signalForSelector:@selector(viewDidAppear:)] subscribeNext:^(id x) { }]; 可以指定某个代理中的方法 [[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple *tuple) { }]; 监听UITextField变化 [textField.rac_textSignal subscribeNext:^(NSString *text) { //文本输入变化 }]; [[textField rac_signalForControlEvents:UIControlEventEditingChanged] subscribeNext:^(id x) { //文本输入变化 }]; [[textField rac_signalForControlEvents:UIControlEventEditingDidEnd] subscribeNext:^(id x) { //结束编辑 }]; RACObserve监听的对象属性返回值作为RAC监听对象属性的值 RAC(customBtn, hidden) = RACObserve(textField, hidden); 等价于: [RACObserve(textField, hidden) subscribeNext:^(BOOL x) { customBtn.hidden = x; }]
2.1> 按钮点击事件
[[submitBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { }];
2.2> 手势事件
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init]; [[cancelTap rac_gestureSignal] subscribeNext:^(id x) { }];
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"postData" object:nil] subscribeNext:^(NSNotification *notification) { }];
信号创建 RACSubject * moreSignal = [RACSubject subject]; 信号发送 [moreSignal sendNext:]; 信号响应 [moreSignal subscribeNext:^(id x) { }];
map函数就是创建一个订阅者的映射并且返回数据,RAC监听对象属性的值,也就是customLabel.text根据textField的值来赋值,value的类型根据target监听属性值来定义
eg:textField的text是NSString类型. map函数需要返回值,类型必须和等号左边的RAC的接受值一致,如果返回BOOL则crash
RAC(customLabel, text) = [textField.rac_textSignal map:^id(NSString *value) { return value; }]; [[textFild.rac_textSignal map:^id(id value) { return @1; }] subscribeNext:^(id x) { NSLog(@"%@", x); //输出1,这个x是上面block中return返回值1 }];
6.1> filter 可以帮助你筛选出你需要的值
[[self.textFild.rac_textSignal filter:^BOOL(NSString *value) { return [value length] > 3; }] subscribeNext:^(id x) { NSLog(@"x = %@", x); }];
6.2> ignore 可以忽略某些值
[[self.textFild.rac_textSignal filter:^BOOL(NSString *value) { return [value length] > 3; }] subscribeNext:^(id x) { NSLog(@"x = %@", x); }];
6.3> take 从开始一共取几次信号. 从头
RACSubject * subject = [RACSubject subject]; [[subject take:2] subscribeNext:^(id x) { NSLog(@"%@",x); // 1 2 }]; [subject sendNext:@"1"]; [subject sendNext:@"2"]; [subject sendNext:@"3"];
6.4> takeLast 取后面的值 必须是发送完成
RACSubject * subject = [RACSubject subject]; [[subject takeLast:2] subscribeNext:^(id x) { NSLog(@"%@",x); // 2 3 }]; [subject sendNext:@"1"]; [subject sendNext:@"2"]; [subject sendNext:@"3"]; [subject sendCompleted];
6.5> takeUntil 当传入的某个信号发送完成,这样就不会再接收源信号的内容,或者发送任意数据也不会再接收
RACSubject * subject = [RACSubject subject]; RACSubject * signal = [RACSubject subject]; [[subject takeUntil:signal] subscribeNext:^(id x) { NSLog(@"%@",x); //1 2 }]; [subject sendNext:@"1"]; [subject sendNext:@"2"]; [signal sendCompleted]; [subject sendNext:@"3"];
6.6> distinctUntilChanged 如果当前的值跟上一个值相同,这样就不会被订阅发送信号
RACSubject * subject = [RACSubject subject]; [[subject distinctUntilChanged] subscribeNext:^(id x) { NSLog(@"%@",x); //A }]; [subject sendNext:@"A"]; [subject sendNext:@"A"];
当我们多次订阅同一个信号的时候,避免订阅信号block中的代码被调用多次。
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { return nil; }]; RACMulticastConnection *connection = [signal publish];//转化为连接类 [connection.signal subscribeNext:^(id x) { }]; [connection.signal subscribeNext:^(id x) { }]; [connection connect]; //链接
当进入一个页面需要发多次请求,当全部请求结束再执行更新UI,可以使用下面RAC方法,可以替代多线程GCD的dispatch_group_enter和dispatch_group_leave 参数1:请求结束执行的方法,参数个数必须是和参数二的数组信号个数一致,是信号发送的值 参数2: 数组 存放所有信号
rac_liftSelector:withSignalsFromArray:
9.1> concat 数组组合
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; RACSequence *concatenated = [letters concat:numbers]; [concatenated.signal subscribeNext:^(id x) { NSLog(@"%@",x); // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9 }];
9.2> merge 当多个信号执行同一种操作 使用merge
RACSubject *subject1 = [RACSubject subject]; RACSubject *subject2 = [RACSubject subject]; RACSignal *mergeSignal = [subject1 merge:subject2]; [mergeSignal subscribeNext:^(id x) { NSLog(@"%@",x); }]; [subject1 sendNext:@"第一个位置调用"]; [subject1 sendNext:@"第二个位置调用"];
9.3> zipWith 当希望两个信号都发出信号时才调用,并且会把两个信号的内容组成一个元组,和第8的作用非常一样
RACSubject *subject1 = [RACSubject subject]; RACSubject *subject2 = [RACSubject subject]; RACSignal *mergeSignal = [subject1 zipWith:subject2]; [mergeSignal subscribeNext:^(id x) { NSLog(@"%@",x); }]; [subject1 sendNext:@"第一个位置调用"]; [subject1 sendNext:@"第二个位置调用"];
9.4> combineLatest 将多个信号合并起来,当希望两个信号都发出信号时才调用,和9.3作用一样
RACSubject *subject1 = [RACSubject subject]; RACSubject *subject2 = [RACSubject subject]; RACSignal *mergeSignal = [RACSignal combineLatest:@[subject1,subject2] reduce:^id(NSString * title1,NSString * title2){ NSLog(@"%@ -- %@",title1,title2); //第一个位置调用 -- 第二个位置调用 return @"返回值"; }]; [mergeSignal subscribeNext:^(id x) { NSLog(@"%@",x); //返回值 }]; [subject1 sendNext:@"第一个位置调用"]; [subject2 sendNext:@"第二个位置调用"];
9.5> reduce reduce是聚合的作用,讲多个信号分别发送的信号聚在一起返回。
ReactiveCocoa的GitHub官网
细说ReactiveCocoa的冷信号与热信号(一)
细说ReactiveCocoa的冷信号与热信号(二): 为什么要区分冷热信
细说ReactiveCocoa的冷信号与热信号(三): 怎么处理冷信号与热信号