由于新入职的团队使用的是RAC,因此需要熟悉一下RAC的类图和大致的实现。 类图大致如下:
RACSequence
和Cocoa内置的集合对象(NSArray,NSSet)类似,内部不能包含nil,是RACStream(一个抽象类,用于表示为信号流的值)的子类,RACSequence
是拉力驱动(被动)的数据流,因此默认是惰性求值,并且当调用map
或falttenMap
之类的方法时,block对内部的对象求值只会进行一次。 借用RAC官方Demo
NSArray *strings = @[ @"A", @"B", @"C" ]; RACSequence *sequence = [strings.rac_sequence map:^(NSString *str) { NSLog(@"%@", str); return [str stringByAppendingString:@"_"]; }]; // Logs "A" during this call. NSString *concatA = sequence.head; // Logs "B" during this call. NSString *concatB = sequence.tail.head; // Does not log anything. NSString *concatB2 = sequence.tail.head; RACSequence *derivedSequence = [sequence map:^(NSString *str) { return [@"_" stringByAppendingString:str]; }]; // Does not log anything, and concatA2 value is A_ ,NOT _A_ NSString *concatA2 = sequence.head;复制代码
RACSignal
RACSignal是专注于解决通过订阅信号来异步进行事件传输 RAC是线程安全的,因此可以在任意线程进行signal发送,但是一个订阅者只能串行的处理一个信号,而不能并发的处理多个信号。 因此-subscribeNext:error:completed:
的 block
不需要进行synchronized
。
bind
利用一段代码来测试bind函数的调用顺序,由于代码结构复杂,所以在bind模块对应的block都会标有数字,方便描述调用顺序。
RACSignal *sourceSig = [RACSignal createSignal:^RACDisposable *(idsubscriber) { //doSomething //... //...block1 NSLog(@"\nbegin---\n%@\n---end",@"dosomething"); [subscriber sendNext:@"hello world"];// [subscriber sendCompleted]; return nil; }]; RACSignal *bindSig = [sourceSig bind:^RACStreamBindBlock{ //block2 return ^(id value, BOOL *stop) { //block3 //这里对value进行处理 return [RACSignal return:value]; }; }]; [bindSig subscribeNext:^(id x) { //block4 NSLog(@"\nbegin---\n%@\n---end",x); }];复制代码
1.createSignal:
的作用是将传的:^RACDisposable *(id<RACSubscriber> subscriber)
这个block存到sourceSig
的didSubscribe
字段中(block1)
2.bind:
通过调用createSignal:
返回一个新的信号bindSig
,bind:
的参数是一个没有入参,返回值为RACStreamBindBlock
的block(block2)。 RACStreamBindBlock入参和出参如下:
typedef RACSignal * _Nullable (^RACSignalBindBlock)(ValueType _Nullable value, BOOL *stop);复制代码
通过改变传入进来的Value(也就是改变block3的内部实现 ),从而实现了flattenMap:
,skip:
,takeUntilBlock:
,distinctUntilChanged:
等高级操作。
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block { //返回bindSig,并将block保存至didSubscribe return [[RACSignal createSignal:^(idsubscriber) { //省略didSubscribe内部代码 }] setNameWithFormat:@"[%@] -bind:", self.name];}复制代码
3.当bindSig
调用subscribeNext:
,生成一个RACSubscriber,并将nextBlock保存在_next中
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock { NSCParameterAssert(nextBlock != NULL); RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL]; return [self subscribe:o];}复制代码
然后bindSig
调用subscribe:
,入参就是这个subscribe
4.在subcribe:
中,调用bindSig
保存的didSubscribe
,执行一长串代码(block5)
return [[RACSignal createSignal:^(idsubscriber) { //block5 RACStreamBindBlock bindingBlock = block(); //这里的self是sourceSig NSMutableArray *signals = [NSMutableArray arrayWithObject:self]; RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) { //block6 BOOL removeDisposable = NO; @synchronized (signals) { [signals removeObject:signal]; if (signals.count == 0) { [subscriber sendCompleted]; [compoundDisposable dispose]; } else { removeDisposable = YES; } } if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable]; }; void (^addSignal)(RACSignal *) = ^(RACSignal *signal) { //block7 @synchronized (signals) { [signals addObject:signal]; } RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; [compoundDisposable addDisposable:selfDisposable]; //4.订阅newSig,然后将newSig的值传给bindSig的订阅者,执行block8 RACDisposable *disposable = [signal subscribeNext:^(id x) { //block8 //这里是subscriber对应的是bindSig [subscriber sendNext:x]; //5.然后执行block4 } error:^(NSError *error) { [compoundDisposable dispose]; [subscriber sendError:error]; } completed:^{ @autoreleasepool { completeSignal(signal, selfDisposable); } }]; selfDisposable.disposable = disposable; }; @autoreleasepool { RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; [compoundDisposable addDisposable:selfDisposable]; //1.先执行block1,然后执行block9 RACDisposable *bindingDisposable = [self subscribeNext:^(id x) { // Manually check disposal to handle synchronous errors. //block9 if (compoundDisposable.disposed) return; BOOL stop = NO; //对sourceSig传的值进行处理,再包装在新值(可为nil)简称newSig //2.再执行block3 id signal = bindingBlock(x, &stop); @autoreleasepool { //3.假如block3返回的sig不为nil执行block7 if (signal != nil) addSignal(signal); //假如block3返回的sig为nil或者stop指针为YES,执行block6 if (signal == nil || stop) { [selfDisposable dispose]; completeSignal(self, selfDisposable); } } } error:^(NSError *error) { [compoundDisposable dispose]; [subscriber sendError:error]; } completed:^{ @autoreleasepool { completeSignal(self, selfDisposable); } }]; selfDisposable.disposable = bindingDisposable; } return compoundDisposable; }] setNameWithFormat:@"[%@] -bind:", self.name];复制代码
总结一下bind的作用:生成一个新的信号bindSig,订阅源信号sourceSig,当sourceSig发送一个值时,bindSig通过订阅收到这个值后,根据上层传的RACStreamBindBlock转换value,发送给bindSig的subscriber。
ATTENTION
由于RACSignal是冷信号,所以每次有新的订阅都会触发副作用(对应的block),这意味着 singal对应的block会执行多次。
__block int missilesToLaunch = 0;// Signal that will have the side effect of changing `missilesToLaunch` on// subscription.RACSignal *processedSignal = [[RACSignal return:@"missiles"] map:^(id x) { missilesToLaunch++; return [NSString stringWithFormat:@"will launch %d %@", missilesToLaunch, x]; }];// This will print "First will launch 1 missiles"[processedSignal subscribeNext:^(id x) { NSLog(@"First %@", x);}];// This will print "Second will launch 2 missiles"[processedSignal subscribeNext:^(id x) { NSLog(@"Second %@", x);}];复制代码
假如想冷信号执行一次,就得转换成热信号。比如网络请求肯定只需要一次就好,所以在业务场景中通过multicast
使用,可以避免冷信号的的多次调用
// This signal starts a new request on each subscription.RACSignal *networkRequest = [RACSignal createSignal:^(idsubscriber) { AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { [subscriber sendError:error]; }]; [client enqueueHTTPRequestOperation:operation]; return [RACDisposable disposableWithBlock:^{ [operation cancel]; }];}];// Starts a single request, no matter how many subscriptions `connection.signal`// gets. This is equivalent to the -replay operator, or similar to// +startEagerlyWithScheduler:block:.// single中除了Subject之外的都是冷信号,Subject是热信号。RACMulticastConnection *connection = [networkRequest multicast:[RACReplaySubject subject]];[connection connect];[connection.signal subscribeNext:^(id response) { NSLog(@"subscriber one: %@", response);}];[connection.signal subscribeNext:^(id response) { NSLog(@"subscriber two: %@", response);}];复制代码
当我们需要在nextBlock之前需要加一些副作用代码,就可以调用-doNext
,这时候会先调用这里的block,再调用subscriber
的sendNext
。
UI事件
RAC(self.label,text,@"nil的值") = [RACSignal createSignal:^RACDisposable *(idsubscriber) { __block int i = 0; [[self.button rac_signalForControlEvents:UIControlEventTouchDown] subscribeNext:^(id x) { i ++; if (i > 3) { [subscriber sendNext:nil]; } else { [subscriber sendNext:@"123"]; } }]; return nil; }];复制代码
通知
当我们用RAC来改写NSNotification
的时候用rac_addObserverForName
: 比如我们需要监听网络状态时
//当网络发生变化后,RAC这个宏会进行keypath绑定,会将self.NetWorkStatus 赋予新值,这时其他利用RACObserve会收到这个变化并作出对应改 RAC(self, NetWorkStatus) = [[[[NSNotificationCenter defaultCenter] rac_addObserverForName:kRealReachabilityChangedNotification object:nil] map:^(NSNotification *notification) { return @([notification.object currentReachabilityStatus]); }] distinctUntilChanged]; //RACObserve接受新值并订阅信号 [RACObserve(self , NetWorkStatus) subscribeNext:^(NSNumber *networkStatus) { @strongify(self); if (networkStatus.integerValue == RealStatusNotReachable || networkStatus.integerValue == RealStatusUnknown) { [self.viewModel showErrorView]; }else{ [self.viewModel request]; } }]; 复制代码
协议
@weakify(self); [[self rac_signalForSelector:@selector(webViewDidStartLoad:) fromProtocol:@protocol(WebViewDelegate)] subscribeNext:^(RACTuple *tuple) { @strongify(self) if (tuple.first == self.webView){ dispatch_main_async_safe(^{ [self showStatusWithMessage:@"Loading..."]; }); } }];复制代码
网络事件(耗时事件)
__block int callCount = 0; 这里因为订阅了两次,所以会调用两次block,因此假如是io类操作,最好将networkSig包装成RACSubject然后通过multicast广播 self.networkSig = [RACSignal createSignal:^RACDisposable *(idsubscriber) { __block int i = 0; callCount ++; //打印两次 NSLog(@"\nbegin---\n callCount ==%d\n---end",callCount ); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ i++; [subscriber sendNext:@(i)]; }); return nil; }]; [self.networkSig subscribeNext:^(id x) { NSLog(@"\nbegin---\nfirst i ==== %@\n---end", x); }]; [self.networkSig subscribeNext:^(id x) { NSLog(@"\nbegin---\nsecond i ==== %@\n---end", x); }];复制代码
改进后:
__block int callCount = 0; self.networkSig = [RACSignal createSignal:^RACDisposable *(idsubscriber) { __block int i = 0; callCount ++; //只会打印一次 NSLog(@"\nbegin---\n callCount ==%d\n---end",callCount ); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ i++; [subscriber sendNext:@(i)]; }); return nil; }]; RACSubject *subject = [RACSubject subject]; RACMulticastConnection *multicastConnection = [self.networkSig multicast:subject]; [multicastConnection connect]; [multicastConnection.signal subscribeNext:^(id x) { NSLog(@"\nbegin---\nfirst i ==== %@\n---end", x); }]; [multicastConnection.signal subscribeNext:^(id x) { NSLog(@"\nbegin---\nsecond i ==== %@\n---end", x); }];复制代码
KVO
//实现self.navigationItem.title 和 self.viewModel.title的单向绑定 RAC(self.navigationItem,title) = RACObserve(self.viewModel, title);复制代码
RACCommand
创建RACCommand的时候需要返回一个signal,当调用execute:
,signal必须调用sendCompleted
或sendError:
,command才能进行下次execute:
初学者可能会想当然如下写代码
//1.先绑定self.button的keypath:enable RAC(self.button,enabled) = [RACSignal combineLatest:@[self.userNameField.rac_textSignal,self.passwordField.rac_textSignal] reduce:^id(NSString *userName,NSString *password){ return @(userName.length >= 8 && password.length >= 6); }]; //2.然后设置button的点击事件 self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { return [self login]; }];复制代码
这时候运行程序的时候报错
这是因为RAC()
这个宏和 button.rac_command
都会调用 setKeyPath:onObject:nilValue:
这个方法。 首次调用时,会通过objc_setAssociatedObject将keypath保存起来,当重复调用相同的keypath的时候会触发 NSCAssert
正确的做法是 RACSignal *buttonEnabled = [RACSignal combineLatest:@[self.userNameField.rac_textSignal,self.passwordField.rac_textSignal] reduce:^id(NSString *userName,NSString *password){ return @(userName.length >= 8 && password.length >= 6); }]; self.button.rac_command = [[RACCommand alloc] initWithEnabled:buttonEnabled signalBlock:^RACSignal *(id input) { return [self login]; }];复制代码