RAC(Part2:SideEffect)

函數編程里一切計算都是為了求值,沒有副作用是一個顯著地特征。從實用角度出發,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的完成,事實上,它不會完成,因為它是代表了這個按鈕可能被點擊的序列。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容