MVCとObserverパターン

MVCとは
Model
 ビジネスロジックを表現する要素.状態を持っている.Controllerから要求を処理し,状態の変更をViewに通知する.ViewやControllerに依存しない.
 SQLや一時保存したデータを書き換えたりする.
View
 UIを担当する.Modelの状態をユーザーに表示し,ユーザーから入力があった場合Controllerに通知する.Modelに依存し,Controllerに依存しない.
 コマンドラインかもしれないしGUIかも知れない.
Controller
 ユーザーの応答に対して処理する要素.Viewから通知を受け取り,Modelに変更の要求する.ModelとViewをもっている.
 直接に描画を行ったり、modelの内部データを直接操作したりはしない。

一般的な制御フローは以下のようになる.

  1. ユーザがユーザインタフェースを通してviewに入力する
  2. ViewがControllerにイベントを通知する
  3. Controllerが通知を受け,イベントを処理する
  4. Controllerが処理によってModelのメソッドを呼ぶ.その結果,Modelの状態が変わることがある
  5. Modelの状態が変更した場合,Viewに変更したことを通知する.
  6. Viewは通知を受けて,Modelのから出力を更新する
  7. Viewがが操作待ちとなる.

 iOSの場合,ModelからControllerに通知をしてViewを更新するのが基本的なやり方なのだが,面倒臭がってModelとViewとControllerのUIViewControllerに実装してしましがちだ.
 特にViewとControllerの境目がない。nibをView、UIViewControllerをControllerにしても良いのだが、それでもUIViewControllerが肥大化してしまう。単に値の入れ替えだけでなく、nibで表現できないことはすべてコードで書かなければいけないからだ。
 そこで、意識的にはUIViewController内のコードをViewとControllerに分けることにする。これは厳密に分ける(違うClassに分ける)のではなく分かれているように見せるだけだ。

まず、ModelからViewに通知する構造を作ります。これには、addObserverとobserveValueForKeyPathを使います。

- (void)viewDidLoad
    Model *model = [[Model alloc] init];
    [model addObserver:self forKeyPath:@"property" options:NSKeyValueObservingOptionInitial context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"property"]) {
        ...
    }
}

 このように書くとModelのメンバー(この場合property)が変更があるたびに自動的にobserveValueForKeyPathが呼ばれ、if文の中でViewが変更します。
 optionsの指定はNew,Old,Initial,Priorの論理和で行う.newとOldはobserveValueForKeyPathのchangeに変更前後の値を含むかを指定できる.InitialはaddObserverしたときに自動的にobserveValueForKeyPath呼ばれ初期化する.Priorは変数の変更直前にもobserveValueForKeyPathが呼ばれるようになる.
 observeValueForKeyPathは、変更された場合すべて同じメソッドが呼べれるので長くなりがちなので、他の更新用のメソッドを呼ぶだけにしておくのが良い。ただそれでも十分に短いとは言えないので、optionにselecterを渡すと良い。

- (void)viewDidLoad
    Model *model = [[Model alloc] init];
    [model addObserver:self forKeyPath:@"property" options:NSKeyValueObservingOptionInitial context:@selector(method)];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    [self performSelector:(SEL)context withObject:nil afterDelay:0.0];
}

 selecterはchar[]なのでvoid*に渡すことができます。observeValueForKeyPath側ではそのselecterを呼び出すだけです。
これで、Viewへの通知ができるようになりました
 他にもViewに通知する方法は幾つかあります。NSNotificationCenter、delegate、Blockです。ですが,ObseverのようにModelを改変せずに実装ません.


 さらに,ViewとControllerを分けたいと思います.これには,Categoryを使います.Categoryは既存のクラスに対してmethodを追加できる機能です.これを応用してViewControllerを複数ファイルに分けることができます.ただし,同じメソッド名がある場合,どれか一つが実装されます.エラーも警告も出ないので非常に注意が必要になります.
 Categoryによる分割は,ファイル数も多くなるし大手を振って良い方法だとはいえませんが,何百行あるよりはマシだと思っています.せめて,気持ちだけでも分けましょう.View部分のあとにコメント等で分けてController部分を書くとかね

実装したサンプルはこちら