函數編程里一切計算都是為了求值,沒有副作用是一個顯著地特征。從實用角度出發,RAC引入了副作用。
Subject
作為一種“可變”(可變值就是一種副作用,函數?編程里一切值都是不可變的,也就沒有變量的概念)的Signal,你可以控制它的值,它就是觀察者模式中的Subject。
RACSubject *animals = [RACSubject subject];
[animals subscribeNext:^(id nextObject) {
NSLog(@"%@", nextObject);
}];
[animals sendNext:@"cat"];
Multicast Connection (publish, multicast, replay)
Signal的副作用一般是在subscribe的時候發生的,并且每次subscribe都會發生,這樣很多時候并不是所期望的,例如一個網絡操作,被subscribe多次也只執行一次,那么我們就需要將這些訂閱連接(connection
)起來,也就是多播。
使用publish連接:
RACSignal *signal = [[RACSignal return:@"hello" ] doNext:^ (id nextValue) {
NSLog(@"nextValue:%@", nextValue);
}];
RACMulticastConnection *connection = [signal publish];
[connection.signal subscribeNext:^(id nextValue) {
NSLog(@"First %@", nextValue);
}];
[connection.signal subscribeNext:^(id nextValue) {
NSLog(@"Second %@", nextValue);
}];
[connection connect];
這段代碼的輸出是:
2014-11-30 15:11:14.711 racdemo[7416:303] nextValue:hello
2014-11-30 15:11:14.712 racdemo[7416:303] First hello
2014-11-30 15:11:14.712 racdemo[7416:303] Second hello
connection connect之后,signal開始發送第一個值,如果connection比subscribe先執行,那么訂閱就收不到任何的值。所以可以將信號connect到一個replay subject:
RACMulticastConnection *connection = [signal multicast:[RACReplaySubject subject]];
[connection connect];
[connection.signal subscribeNext:^(id nextValue) {
NSLog(@"First %@", nextValue);
}];
[connection.signal subscribeNext:^(id nextValue) {
NSLog(@"Second %@", nextValue);
}];
其實最簡單的做法是使用replay:
RACSignal *replaySignal = [signal replay];
[replaySignal subscribeNext:^(id nextValue) {
NSLog(@"First %@", nextValue);
}];
[replaySignal subscribeNext:^(id nextValue) {
NSLog(@"Second %@", nextValue);
}];
publish, multicast和replay這幾個操作其實都是同一個概念:
- (RACMulticastConnection *)publish {
RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
RACMulticastConnection *connection = [self multicast:subject];
return connection;
}
- (RACMulticastConnection *)multicast:(RACSubject *)subject {
[subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
return connection;
}
- (RACSignal *)replay {
RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name];
RACMulticastConnection *connection = [self multicast:subject];
[connection connect];
return connection.signal;
}
這里還有一個cold/hot signal的說法,signal默認是cold的,在每次subscribe的時候才會工作;當一個connection建立之后,這個signal就是hot的,在訂閱之前已經處于活動狀態。
Command
對于UI組件,例如一個按鈕來說,點擊的時候要引起副作用。RAC使用了RACCommand?,方便封裝這種副作用。
self.ClickMe.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id input) {
NSLog(@"button pressed");
return [RACSignal empty];
}];
上面的例子并不能很好的說明RACCommand的本質,一個較為完備的例子:
self.ClickMe.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id input) {
RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"doing something"];
[subscriber sendCompleted];
});
return nil;
}];
return signal;
}];
[self.ClickMe.rac_command.executionSignals subscribeNext:^(id doSomethingSignal) {
NSLog(@"button pressed, let's do something");
[doSomethingSignal subscribeCompleted:^{
NSLog(@"done!");
}];
}];
signalBlock是一個返回signal的動作,這個動作在按鈕被點擊的時候執行,返回的signal連接一個RACReplaySubject,然后再executionSignal上訂閱。
另外,在sendCompleted之前,按鈕處于禁用狀態。
每次執行動作都會發送一個新的signal,在executionSignals的next中注冊這個signal的完成,而不是注冊executionSignals的完成,事實上,它不會完成,因為它是代表了這個按鈕可能被點擊的序列。