チームでiOSアプリを開発する場合、アプリ全体のコード・クラス設計等に統一感が出るよう開発ルールを決めることが多いかと思います。
今回は私がiPadアプリを開発するに当たって決めた開発ルールの中でうまくいったなと思うモノを紹介します。
##前提
開発ルールを適用したプロジェクトの概要は以下です。異なる性質のプロジェクトでは有用でないかも知れないので注意してください。
- iPadアプリ
- 規模
- コード行数は5万行程度(コメント等も含めた数字)
- 開発者だけで7名のチーム
- 詳細設計〜単体テストだけで工数30人月程度
- 開発者のスキルレベルはiOS経験が数年ある人が半分、他のプラットホームで開発経験はあるが、iOSは初めてという人が半分
##開発ルール
###1.基本的にViewControllerには1対1でDataControllerを作り、すべての状態をそこで管理する
理由はViewControllerを薄くするためです。
objc.ioの記事で、UITableViewDataSourceをViewControllerでなく、別クラスに実装することでViewControllerを軽くするという方法が紹介されていますが、これはその適用範囲をさらに広げたものです。
ViewControllerには基本的に一切の状態を持たせず、NSObjectのサブクラスとしてDataControllerクラスを定義しそこですべての状態を管理します。
DataControllerの責務は以下です。
- 状態を管理する
ユーザからの入力、サーバから受信したデータなどの状態を保持します。その変更は基本的にDataController内でのみ行います(DataController以外のクラスはDataControllerのメソッドを呼ぶことで変更を行う) - サーバとの通信を通信層に委譲する
受信したデータをキャッシュする、受信したデータをモデルにマッピングする、サーバのAPIに合わせてエンドポイントを決めたり送受信するデータのフォーマットを決めたりするといった責務は通信層が負うのでDataControllerは関知しません。DataControllerが行うのはサーバに送信するデータを通信層に渡すこと、通信層からもらったモデルを保持し、ViewControllerに状態が変更されたことを通知すること、通信のエラー処理です。
DataControllerという名前にした理由は以下です。
- ViewControllerとの対応がわかりやすい
例えば、AccountViewController, AccountDataControllerのような名前になるので対応するViewControllerがわかりやすいです。 - TableViewと関係ない状態も持つのでDataSourceという名前にしたくなかった
###2.Subviewの更新はViewクラスにモデルを渡してViewクラス内部で行う
悪い例はview.nameLabel.text = @"myName";
のようなものです。
理由はカプセル化するためです。ごく基本的なことですが意外と守られていない気がします。
特にUITableViewCellのサブクラスの更新をcellForRowAtIndexPathで行っているためにcellForRowAtIndexPathが長くなってしまうのはよく見るアンチパターンです。以下の例のように、ControllerからはbindModelメソッドでモデルを渡すだけにします。
(勿論このルールはCell以外のViewについても適用されます。)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TodoCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
TodoCellModel *todo = [self.todos objectAtIndex:indexPath.row];
[cell bindModel:todo];
return cell;
}
###3.UITableViewCellのサブクラスに対応するViewModelを作り、そこにCellに表示する状態をすべて持たせる
Cellのselectedフラグなど、ユーザからの操作で変化する状態などもViewModelに持たせます。
ViewModelを作る理由は以下です。
- Viewを薄く、Modelを厚くしてテストを容易にするため。文字列のフォーマットのテストなどで重宝しました。
- キャッシュするため。パフォーマンスチューニングを行うときに計算結果や文字列のフォーマットの結果をViewModelにキャッシュすると非常に効果がありました。CellはReuseされるのでキャッシュができません。
###4.クラスは短く、メソッドも短く
1クラス150行まで、1メソッド20行までを目安にしていました。
妥協したところもありますが、8割程度はこの制限内に収まっていました。
短くするだけでとてもわかりやすくなるのでおすすめです。
Kent Beckは1メソッド3行程度で書くそうですがまだまだその境地には至れていません。。
##まとめ
如何でしたでしょうか。参考になりましたら幸いです。
フィードバックなどありましたら@anton0825またはブログまでお願い致します。