因爲新入職的團隊使用的是RAC,所以須要熟悉一下RAC的類圖和大體的實現。 類圖大體以下:web
和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是專一於解決經過訂閱信號來異步進行事件傳輸 RAC是線程安全的,所以能夠在任意線程進行signal發送,可是一個訂閱者只能串行的處理一個信號,而不能併發的處理多個信號。 所以-subscribeNext:error:completed:
的 block
不須要進行synchronized
。bash
利用一段代碼來測試bind函數的調用順序,因爲代碼結構複雜,因此在bind模塊對應的block都會標有數字,方便描述調用順序。網絡
RACSignal *sourceSig = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//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:
等高級操做。async
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block {
//返回bindSig,並將block保存至didSubscribe
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
//省略didSubscribe內部代碼
}] setNameWithFormat:@"[%@] -bind:", self.name];
}
複製代碼
3.當bindSig
調用subscribeNext:
,生成一個RACSubscriber,並將nextBlock保存在_next中ide
- (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:^(id<RACSubscriber> subscriber) {
//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。
因爲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:^(id<RACSubscriber> subscriber) {
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
。
RAC(self.label,text,@"nil的值") = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
__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 *(id<RACSubscriber> subscriber) {
__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 *(id<RACSubscriber> subscriber) {
__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);
}];
複製代碼
//實現self.navigationItem.title 和 self.viewModel.title的單向綁定
RAC(self.navigationItem,title) = RACObserve(self.viewModel, title);
複製代碼
建立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];
}];
複製代碼