ReactiveCocoaをかじってみた


はじめに
----
最近[ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa)というものを知りました。これはリアクティブプログラミングというパラダイムのCocoa実装のようです。リアクティブプログラミングそのものは[なぜリアクティブプログラミングは重要か。](http://d.hatena.ne.jp/pokarim/20101226)を読むと、分かったような分からないようなもやもやした状態になります。
もやもやした状態は、自分で実験していく事で分かるかもしれません。なので手を動かしてReactiveCocoaをかじってみます。

試しに作るもの
----
[ReactiveCocoa : NSHipster](http://nshipster.com/reactivecocoa/)のサンプルを見つつ、教科書的なユーザ作成画面を作ってみます。

- username email password passwordVerification のTextField
- createボタンは以下の条件を満たしたら押せる(ルール１)
    - username,emailが空でない
    - passwordとpasswordVerificationが同じ
    - passwordは8文字以上
- passwordとpasswordVerificationが違う場合とpasswordが8文字以下の場合は、Labelに警告が表示される(ルール２)

![RACsample.gif](https://qiita-image-store.s3.amazonaws.com/0/7946/84d40b8f-0e9c-ef41-0db3-298f430750ee.gif "RACsample.gif")


Target/Action方式で実装してみる
----
まずは、作り慣れたTarget/Actionの方法で実装してみます。ViewControllerに、ボタンのenableを操作するupdateButtonEnable:メソッドと、Labelを書き換えるupdatePasswordMessage:メソッドを実装します。また、それぞれのTextFieldにaddTargetしています。


```objective-c:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // ルール1
    [self.usernameField addTarget:self
                           action:@selector(updateButtonEnable:)
                 forControlEvents:UIControlEventEditingChanged];
    [self.emailField addTarget:self
                        action:@selector(updateButtonEnable:)
              forControlEvents:UIControlEventEditingChanged];
    [self.passwordField addTarget:self
                           action:@selector(updateButtonEnable:)
                 forControlEvents:UIControlEventEditingChanged];
    [self.passwordVerificationField addTarget:self
                                       action:@selector(updateButtonEnable:)
                             forControlEvents:UIControlEventEditingChanged];

    // ルール2
    [self.passwordField addTarget:self
                           action:@selector(updatePasswordMessage:)
                 forControlEvents:UIControlEventEditingChanged];
    [self.passwordVerificationField addTarget:self
                                       action:@selector(updatePasswordMessage:)
                             forControlEvents:UIControlEventEditingChanged];
    
}



// ルール1 createボタンのenable条件
-(void)updateButtonEnable:(UITextField *)field
{
    NSLog( @"%@,%@,%@,%@",
          self.usernameField.text,
          self.emailField.text,
          self.passwordField.text,
          self.passwordVerificationField.text);
    
    self.createButton.enabled =
    [self.usernameField.text length] > 0 &&
    [self.emailField.text length] > 0 &&
    [self.passwordField.text length] >= 8 &&
    [self.passwordField.text isEqual:self.passwordVerificationField.text];

}

// ルール2 passwordの条件
-(void)updatePasswordMessage:(UITextField *)field
{
    NSString *message = @"";
    if( [self.passwordField.text length] < 8){
        message = @"need 8 char.";
    }
    if( ![self.passwordField.text isEqualToString:self.passwordVerificationField.text] ){
        message = @"need same password";
    }
    self.passwordMessageLabel.text = message;
}


```

絵にするとこんな感じです。
![RAC-2.png](https://qiita-image-store.s3.amazonaws.com/0/7946/2001db76-3037-8f91-b2e9-2f5df1b70bb6.png "RAC-2.png")


ReactiveCocoa方式で実装してみる
----
次はReactiveCocoaで実装します。Target/Action方式の時に作ったメソッドを、ReactiveCocoaではRACSignalオブジェクトとして実装します。またaddTargetする代わりにRAC()マクロを使ってバインドします。

```objective-c

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // ルール1 createボタンのenable条件
    RACSignal *formValidSignal =
    [RACSignal combineLatest:@[ self.usernameField.rac_textSignal,
                                self.emailField.rac_textSignal,
                                self.passwordField.rac_textSignal,
                                self.passwordVerificationField.rac_textSignal]
                      reduce:^(NSString *username,
                               NSString *email,
                               NSString *password,
                               NSString *passwordVerification) {
                          NSLog( @"%@,%@,%@,%@",
                                username,
                                email,
                                password,
                                passwordVerification);
                          
                          return @([username length] > 0 &&
                          [email length] > 0 &&
                          [password length] >= 8 &&
                          [password isEqual:passwordVerification]);
                      }];
    
    RAC(self.createButton,enabled) = formValidSignal;
    
    // ルール2 passwordの条件
    RACSignal *passwordValidSignal =
    [RACSignal combineLatest:@[ self.passwordField.rac_textSignal,
                                self.passwordVerificationField.rac_textSignal]
                      reduce:^(NSString *password,
                               NSString *passwordVerification) {
                          NSString *message = @"";
                          if( [password length] < 8){
                              message = @"need 8 char.";
                          }
                          if( ![password isEqualToString:passwordVerification] ){
                              message = @"need same password";
                          }
                          return message;
                      }];
    RAC(self.passwordMessageLabel,text) = passwordValidSignal;
}


```
絵にすると、Target/Action方式とほぼ同じです。
![RAC-2.png](https://qiita-image-store.s3.amazonaws.com/0/7946/28658135-3f92-8d13-b4e4-9214a291a3c1.png "RAC-2.png")




Target/Action方式とReactiveCocoa方式を見比べる
----
ReactiveCocoa方式では、UITextFieldにrac_textSignaleというプロパティが生えています。またRACSignalのreduce:の戻り値が、RAC()マクロでバインドされたプロパティに値をセットするようです。
ルールの実装が、Target/Action方式では「メソッドに実装」されており、ReactiveCocoaでは「オブジェクトに実装」されているように見えます。
Target/Action方式では、ルールにメソッドの中から直接UIのオブジェクトにアクセスしています。これは「ルール部分とUI部分が癒着している」ようにも見えます。ルールが複雑になると、ここが肥大化して行くのだろうなぁと思います。
一方ReactiveCocoa方式では、ルールがRACSignalとして独立しており、UIとの分離が行われています。ただソースコードの煩雑さが増しています。手放しで喜べるものでもないですね。

RACSignalの連鎖
----
RACSignalは連鎖する事が出来ます。 
ここまでのサンプルでは、ルール１と２に、「パスワードが８文字以上」「パスワードが同じか」のロジックが重複して書かれています。これをRACSignalの連鎖を使って分離します。
RACSignalで拾える「状態変化」は、KVOやUIのイベントNSNotification等いろいろ拾えるようですが、RACSignalそのものも拾えます。なので「パスワードが８文字以上かどうか」というRACSignalと、「パスワードが同じかどうか」というRACSignalを作って、連鎖させてみます。

ややこしいのでまずは絵を見せます。
![RAC-2.png](https://qiita-image-store.s3.amazonaws.com/0/7946/fd9378e8-cf85-60c3-c0b2-c3c4d5be149d.png "RAC-2.png")

```objective-c:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    RACSignal *samePasswordSignal =
    [RACSignal combineLatest:@[self.passwordField.rac_textSignal,
                               self.passwordVerificationField.rac_textSignal]
                      reduce:^(NSString *password,
                               NSString *passwordVerification){
                          return @([password isEqualToString:passwordVerification]);
                      }];
     
    RACSignal *passwordLengthSignal =
    [RACSignal combineLatest:@[self.passwordField.rac_textSignal]
                      reduce:^(NSString *password){
                          return @([password length]>=8);
                      }];
    
    // ルール1 createボタンのenable条件
    RACSignal *formValidSignal =
    [RACSignal combineLatest: @[self.usernameField.rac_textSignal,
                                self.emailField.rac_textSignal,
                                samePasswordSignal,
                                passwordLengthSignal]
                      reduce:^(NSString *username,
                               NSString *email,
                               NSNumber *passwordsame,
                               NSNumber *passwordLength) {
                          NSLog( @"%@,%@,%@,%@",
                                username,
                                email,
                                passwordsame,
                                passwordLength);
                          return @([username length] > 0 &&
                          [email length] > 0 &&
                          [passwordsame boolValue] &&
                          [passwordLength boolValue]);
                      }];
    
    RAC(self.createButton,enabled) = formValidSignal;
    
    // ルール2 passwordの条件
    RACSignal *passwordValidSignal =
    [RACSignal combineLatest:@[samePasswordSignal,
                               passwordLengthSignal]
                      reduce:^(NSNumber *same,
                               NSNumber *length){
                          NSString *message = @"";
                          if( ![length boolValue]){
                              message = @"need 8 char.";
                          }
                          if( ![same boolValue] ){
                              message = @"need same password";
                          }
                          return message;
                      }];
    RAC(self.passwordMessageLabel,text) = passwordValidSignal;
}

```
RACSignalの生成時に、combineLatestに別のRACSignalを設定しておけば、そのRACSignalにも反応するため、Signal→Signal→ と連鎖させる事が出来ます。
BOOL型で値を返そうとしてもNSBoolといったオブジェクトが無いので、NSNumblerのboolValueを使っています。

この、Signalが連鎖していく所は、通常のObjective-Cで書く場合に実装しにくい所だと思います。また、Signalの連鎖をfilterで止めたりする事も出来るようです。まだ自分はそこまで理解できてませんが。

さいごに
----
ReactiveCocoaは「リアクティブプログラミング」というパラダイムの１実装のようです。このパラダイムは「オブジェクト指向プログラミング」や「関数型プログラミング」と同じレベルの概念で、正直難しくて分かっていません。ですが「関係性を記述する」という考え方はとても魅力的です。参照：[2010-12-26 なぜリアクティブプログラミングは重要か。](http://d.hatena.ne.jp/pokarim/20101226)

ReactiveCocoaを「便利なライブラリ」として見た場合、「状態の変化をトリガーに、他の状態を書き換える」ために使えます。これはMacOS用アプリのCocoaBindingに似ていると思います。
またSignalの連鎖は、普通に作ったときにはあまり思いつかないアイデアです。UIの「ビジネスロジック」部分が複雑でややこしくなる場合、このアイデアでルールを分解して組み合わせる、という使い方が出来ると思います。

一方でReactiveCocoaは、使うには「複雑」なライブラリだと思います。これは元々の「リアクティブプログラミング」というパラダイムを「ライブラリ」としてObjective-Cに組み込んでいるので、Objective-Cと馴染んでおらず、ソースコードの複雑さが増しているように思います。

今現在は、ReactiveCocoaは「使い所が難しい」というイメージで落ち着いています。

参考にしたページ
---
- リアクティブプログラミング
    - [2010-12-26 なぜリアクティブプログラミングは重要か。](http://d.hatena.ne.jp/pokarim/20101226)
        - 上記blogの中の、この絵が象徴的です。 ~~この絵の「関数」が、RACSignalなんだと自分は理解しています。~~
        - コメントで指摘もらいました。この絵の中の「振る舞い」がRACSignalに該当しているそうです。
        - ![ふるまい図](http://cdn-ak.f.st-hatena.com/images/fotolife/p/pokarim/20101223/20101223194537.jpg)
- ReactiveCocoa
    - https://github.com/ReactiveCocoa/ReactiveCocoa
    - [Function Reactive Programming Framework - Reactive Cocoa | Cocoaの日々情報局](http://cocoadays-info.blogspot.jp/2013/02/function-reactive-programming-framework.html)
    - [ReactiveCocoaについて - qiita](http://qiita.com/somtd@github/items/8409ddd6d0927c04c1dd)
    - [今回作成したコード](https://github.com/taktamur/RACSample)