1. ikesyo
Changes in body
Source | HTML | Preview
@@ -1,259 +1,259 @@
はじめに
----
最近[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ボタンは以下の条件を満たしたら押せる(ルール1)
- username,emailが空でない
- passwordとpasswordVerificationが同じ
- passwordは8文字以上
- passwordとpasswordVerificationが違う場合とpasswordが8文字以下の場合は、Labelに警告が表示される(ルール2)
![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は連鎖する事が出来ます。
ここまでのサンプルでは、ルール1と2に、「パスワードが8文字以上」「パスワードが同じか」のロジックが重複して書かれています。これをRACSignalの連鎖を使って分離します。
RACSignalで拾える「状態変化」は、KVOやUIのイベントNSNotification等いろいろ拾えるようですが、RACSignalそのものも拾えます。なので「パスワードが8文字以上かどうか」という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){
+ // 1つのシグナルなので -combineLatest:reduce: を使用する必要はありません。
+ RACSignal *passwordLengthSignal = [self.passwordField.rac_textSignal
+ map:^(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は「リアクティブプログラミング」というパラダイムの1実装のようです。このパラダイムは「オブジェクト指向プログラミング」や「関数型プログラミング」と同じレベルの概念で、正直難しくて分かっていません。ですが「関係性を記述する」という考え方はとても魅力的です。参照:[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なんだと自分は理解しています。
- ![ふるまい図](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)