さらに上を目指すための ios アプリ設計

51
さらに上を目指すための iOS アプリ設計 @taketo1024

Upload: taketo-sano

Post on 06-Aug-2015

14.255 views

Category:

Software


0 download

TRANSCRIPT

Page 2: さらに上を目指すための iOS アプリ設計

(このスライドはヤフー社内で「中級者iOSアプリ開発者」向けに行った講義の資料です)

Page 3: さらに上を目指すための iOS アプリ設計

本講座の目的• iOSアプリ開発者がより高い視点からアプリの設計を考えられるようになること。

• デザインパターンなどのお堅い話ではなく、「いい設計」を感覚的に納得できるようにしたい。

• 「いい設計」を意識できるようになり、プロダクトの品質と開発スピードが上がれば嬉しいです。

Page 4: さらに上を目指すための iOS アプリ設計

Agenda1. アプリ開発における「いい設計」とは?

2. Storyboard / xib / コードの住み分け

3. delegate / callback / notification の使い分け

4. インターフェース ~ 晒すものと隠すもの

5. 肥大化したクラスをスリム化する

6. 通信処理のカプセル化と使い捨て

7. 外部ライブラリの使用を判断するポイント

8. おまけ:これからの Swift 対応のために

Page 5: さらに上を目指すための iOS アプリ設計

1. アプリ開発における「いい設計」とは?

Page 6: さらに上を目指すための iOS アプリ設計

やってはいけないこと

最初から「神殿のような設計」を求めない方がいい。

Page 7: さらに上を目指すための iOS アプリ設計

アプリ開発において受け入れるべき 「3つの変化」

1) OS/デバイス/開発言語/フレームワークの進化

• 最新のものでも1年後には当たり前のように陳腐化している。

• ARC がなかった時代、block がなかった時代とでは「最適な設計」は当然違う。

2) ユーザニーズ/トレンドの変化

•「最適な設計」が完成する頃にはもうそれ自体不要になっていたりする。

• アプリ全体に影響を与えるようなフレームワークの採用には注意(例:Three20)。

3) チーム/メンバーの変化

• 人材流動化の時代。いつでもメンバーは入ったり抜けたりする。

• サービスやチームの規模も大きくなれば、設計のあるべき形も当然変わる。

Page 8: さらに上を目指すための iOS アプリ設計

変化に対応できる「いい設計」の要件

1) 柔軟性

• プロダクトの要件や仕様の変更にすぐ対応できる。

• 特定のライブラリやフレームワークにできるだけ依存しない。

2) 拡張可能性

• 一極集中せず、機能を並列的に付け足していける。

• 逆に不要になったものは簡単に取り外せる。

3) 安定性

• 何かをちょっと変えただけで落ちまくるようになっては困る。

Page 9: さらに上を目指すための iOS アプリ設計

参考:「メタボリズム」

建築には空間的な制約があるが、ソフトウェアは真にメタボリックな設計が実現可能。

メタボリズムは、1959年に黒川紀章や菊竹清訓ら日本の若手建築家・都市計画家グループが開始した建築運動。新陳代謝(メタボリズム)からグループの名をとり、社会の変化や人口の成長に合わせて有機的に成長する都市や建築を提案した。

彼らの構想した将来の都市は、高度経済成長という当時の日本の人口増加圧力と都市の急速な更新、膨張に応えるものであった。

彼らは、従来の固定した形態や機能を支える「機械の原理」はもはや有効的でないと考え、空間や機能が変化する「生命の原理」が将来の社会や文化を支えると信じた。 …

引用:Wikipedia 中銀カプセルタワービル黒川紀章

Page 10: さらに上を目指すための iOS アプリ設計

僕はこう思うッス

慣れない内はまずはスピード優先で作っていい。開発速度

が鈍ってきたらリファクタリングを検討しよう。そのとき

にちゃんと時間を取らせてもらえるようにマネージャとの

信頼関係を築いておくことも大事です。

Page 11: さらに上を目指すための iOS アプリ設計

2. Storyboard / xib / コード の住み分け

Page 12: さらに上を目指すための iOS アプリ設計

あかんパターンその1. 激重スパゲッティストーリーボード

• 画面数が多くなると重く、可視性も悪くなる。

• チーム作業だとコンフリクトしまくってやばい。

Page 13: さらに上を目指すための iOS アプリ設計

あかんパターンその2. 全部コードで書いてる

• UI の微調整が大変になる。

• チーム作業の場合つらい。デザイナとの分業もできない。

- (void)viewDidLoad { [super viewDidLoad]; UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(10, 200, 300, 40)]; textField.borderStyle = UITextBorderStyleRoundedRect; textField.font = [UIFont systemFontOfSize:15]; textField.placeholder = @"Message"; textField.autocorrectionType = UITextAutocorrectionTypeNo; textField.keyboardType = UIKeyboardTypeDefault; textField.returnKeyType = UIReturnKeyDone; textField.clearButtonMode = UITextFieldViewModeWhileEditing; textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; textField.delegate = self; [self.view addSubview:textField]; … }

Page 14: さらに上を目指すための iOS アプリ設計

なかなか最適解がない

• Storyboard のいいところ

• アプリの画面構成・遷移が一望できる。

• 簡単な遷移ならコードを書かなくてもいい。

• UI をグラフィカルに構成していける(デザイナと分業できる)。

• もどかしいところ

• 画面遷移も構成も一個のファイルに詰め込んだら当然重くなる。

• 文字列の ID でコードと紐付けなきゃいけない。

• 遷移間の処理は prepareForSegue:sender: で分岐しなきゃいけない。

Page 15: さらに上を目指すための iOS アプリ設計

僕のオススメのやり方

• Storyboard は画面遷移を定義するためだけに使う。

• 各コントローラの UI 構成は xib で作る。

• 画面の遷移処理は performSegueWithID: でやる。

Page 16: さらに上を目指すための iOS アプリ設計

Storyboard で画面遷移、xib で画面構成

Viewを外しておく

対応するファイル名の xib が自動で読み込まれる!

Page 17: さらに上を目指すための iOS アプリ設計

これで全体の画面遷移と各々の画面構成が分離できる

Storyboard で画面遷移、xib で画面構成

Page 18: さらに上を目指すための iOS アプリ設計

Storyboard 非対応な外部ライブラリもこの方法で行ける

#import "XYZStoryboardSegue.h"

@implementation XYZStoryboardSegue

- (void)perform { // 具体的な処理 }

@end

独自 UIStoryboardSegue を作れば、どんな遷移も繋げる

Page 19: さらに上を目指すための iOS アプリ設計

performSegueWithId: で遷移処理

- (void)cellTapped:(XYZTableViewCell *)cell { XYZWebEntity *entity = cell.entity;

[self performSegueWithIdentifier:@"Browser" preparation:^(UIStoryboardSegue *segue) {

XYZWebViewController *vc = segue.destinationViewController; vc.title = entity.title; vc.URL = entity.linkURL; }]; }

• prepareForSegue: に遷移処理をまとめて分岐させるのは嫌なので、遷移時にブロックを渡せるように拡張した(method-swizzling によるちょい裏技)。

• StoryboardID を別ファイルにしたり、遷移処理をメソッド化してカテゴリと切り出したりすれば、各 ViewController では Storyboard のこと意識せずに済む。

Page 20: さらに上を目指すための iOS アプリ設計

• このやり方の難点

• xib だと navigationItem を指定したりできない(Outlet で繋いでおいてコードから指定することはできる)

• 画面遷移処理は全てコードで記述しなきゃいけない。

• その他のやり方

• xib を使わず 画面遷移用の Storyboard と各画面別の Storyboard を分ける。

• Segue は使わず、 instantiateViewControllerWithIdentifier: によってコードで VC を生成して画面遷移。

Page 21: さらに上を目指すための iOS アプリ設計

僕はこう思うッス

現状最適なソリューションはないので、画面の数やチーム人数に応

じて上手くやってける方法を探っていきましょう。

Page 22: さらに上を目指すための iOS アプリ設計

3. delegate / callback / notification の 使い分け

Page 23: さらに上を目指すための iOS アプリ設計

原則:相互参照は良くない

• 特殊なケースを除いて、オブジェクトが相互に参照しあってメッセージを送り合うのは密結合になってよくない。

• アプリは基本的に木構造になっているので、子が親を参照したり、子同士で参照しあったりしないように気をつけたい。

• オブジェクト間の依存関係を必要最小限に制限しながら通信する方法としてある delegate / callback / notification の3パターンを解説します。

子 子

××

Page 24: さらに上を目指すための iOS アプリ設計

A) delegate パターン

• UITextFieldDelegate, UITableViewDelegate, UIImagePickerControllerDelegate など。

• 子(委譲する側 = delegater)は親(委譲される側 = delegate)の詳細については知らず、必要に応じて問い合わせ/丸投げする。

• TableViewController(親)が TableView(子)を持つような、親が子を管理していてその存在期間中に密なやり取りがある場合に有効。

• 1対N には不向き(親のメソッド内で分岐が必要になる)

• VC 間の通信も delegate パターンになっていることが多い。

親 (delegate)

子 (delegater)

子は無責任に問い続ける

Page 25: さらに上を目指すための iOS アプリ設計

B) callback パターン

• UIAlertControllerAction, …WithCompletionHandler: など。

• 親が子に対してやっておいて欲しい処理を予め指定しておく。

• 子への命令とコールバックをまとめて書けるのが利点。

• 通信などの単発の命令の完了処理や、処理中に密なやり取りをする場合に有効。

• AlertView / ActionSheet が callback パターンになったのは必然。

用が済んだらサイナラ

Page 26: さらに上を目指すための iOS アプリ設計

C) notification パターン

• UIApplicationWillEnterForegroundNotification, UIKeyboardWillShowNotification など。

• 通知者が受信者が誰なのかを直接知らない場合に有効。

• 受信者からのレスポンスを受け取りたい場合は使えない。

• ある画面でコンテンツを Like したのが、他画面でも反映されてて欲しい場合などに有効。

• コード上では依存関係が見えにくいので、仕様変更による不具合には注意が必要。

NotificationCenter

親1 親2 親3

(親子という表現は適切でないが、前の二つとの関連のため)

Page 27: さらに上を目指すための iOS アプリ設計

どのパターンにせよ、 「子は親のことを知らなくていい」ことが大事!

delegate

callback

notification

NotificationCenter

親1 親2 親3

Page 28: さらに上を目指すための iOS アプリ設計

僕はこう思うッス

いちいちプロトコルを作るのは面倒だから直接参照したくなるけど、

放っておくとすぐスパゲッティ化するので、早いうちから不必要な

依存性はシャキッと断ち切っておきましょう。

Page 29: さらに上を目指すための iOS アプリ設計

4. インターフェース ~ 晒すものと隠すもの

Page 30: さらに上を目指すための iOS アプリ設計

公開プロパティ/メソッドは できるだけ少なくシンプルにしておく。

• クラスの内部状態/内部でのみ使う操作は private にしておきましょう(Obj-C なら無名カテゴリ/Swift なら private で)。

• 外から変更されることのない property は readonly / immutable に。

• サブクラス化前提のクラスで、子クラスのみに公開する必要のあるものも public にしない。(Swift なら internal で、Obj-C はサブクラス専用ヘッダを用意する)

• 「このクラスの役割は何なのか」を意識し、「適切な名前がつけられるかどうか」を正しい設計の指針にする。

Page 31: さらに上を目指すための iOS アプリ設計

僕はこう思うッス

細かなコーディング規約よりもインターフェースの正しさを意識する

方がずっと大事。

クラスの内部実装が汚いのは直しようがあるが、インターフェースが

イビツだと修正が大変(内装のリフォーム < 骨格の改築)。

Page 32: さらに上を目指すための iOS アプリ設計

5. 肥大化したクラスをスリム化する

Page 34: さらに上を目指すための iOS アプリ設計

BaseViewController の危険性

• 共通処理をなんでもかんでも BaseVC に入れると、どんどん Fat 化してどこに何があるのか分からなくなる。メンテナンス性も下がるし、変更の影響が見えづらく危険。

• 共通処理のうち一部カスタマイズできようにしようとする必要が出てきたりして、サブクラスで共有のパラメータを用意したりテンプレートメソッドを作るハメになって余計ややこしくなる。

• iOS がアップデートして BaseVC でやってる処理が不要になっても容易に取り外せなかったりする。

Page 35: さらに上を目指すための iOS アプリ設計

スリム化の戦略をいくつか

A. モジュール化して切り出し

B. 基底クラスをカテゴリ拡張

C. クラスの実装を分割

Page 36: さらに上を目指すための iOS アプリ設計

A. モジュール化して切り出し

• 例えば TableVC / CollectionVC の delegate / dataSource を別クラスにする。

• 切り出したモジュールとの間での相互参照が増えすぎないように気をつけないといけない(意外とこれが難しいので何でも切り出せばいいというものでもない)。

• 切り出したモジュールが専用の protocol を用意して、親への参照をなくしたりする。

TableViewController

TableViewDelegate

TableViewDataSource

TableViewDelegate

TableViewDataSource

TableViewController

Page 37: さらに上を目指すための iOS アプリ設計

B. 基底クラスをカテゴリ拡張

• 共通処理のうち、独立性の高い機能を切り出して UIViewController 拡張にする。

• ロギング、キーボード表示関連、アラート/HUD表示 など全画面共通の機能。

• これも UIViewController+Common などとするとすぐ Fat 化するので注意。

MyViewController

共通処理 1

共通処理 2

UIViewController + ...

共通処理 1

UIViewController + ...

共通処理 2

Page 38: さらに上を目指すための iOS アプリ設計

C. クラスの実装を分割

• Extension は既存クラスを拡張するためだけでなく、自分で作ったクラスの実装を分割するためにも使える。

• 例えば AppDelegate にはいろんな機能が詰め込まれがちだけど、互いに無関係なものが多いので、機能群をまとめて分割するといい。

MyClass

まとまり1

まとまり2

MyClass

MyClass + まとまり1

MyClass + まとまり2

Page 39: さらに上を目指すための iOS アプリ設計

僕はこう思うッス

最初から何でもかんでも共通化/クラス分割しようとすると危険。

特にクラス階層を深くするのは慎重に。全体像が見えてないうちは

ベタ書きのまま我慢。

美意識よりも効果を優先しましょう。

Page 40: さらに上を目指すための iOS アプリ設計

6. 通信処理のカプセル化と使い捨て

Page 41: さらに上を目指すための iOS アプリ設計

通信処理のカプセル化

• Controller / Model に生の通信処理を書くべきではない。

• endpoint-URL や、リクエストパラメータ、レスポンスの仕様について利用者が意識しなくて済むようにしたい。

• レスポンスは Dictionary などのプリミティブ型ではなく、専用のエンティティクラスを用意した方がいい。型セーフになるし API の仕様変更に対しても柔軟に対応できる。

Page 42: さらに上を目指すための iOS アプリ設計

僕が好きなやり方: 通信自体をオブジェクトとして使い捨てる

- (IBAction)searchButtonTapped:(UIButton *)button { NSString *query = …; XYZSearchRequest *req = [XYZSearchRequest requestWithQuery:_query];

[req startWithHandler:^(XYZSearchResultSet *result, NSError *error) { // 通信コールバック if(error) { … // エラー処理 return; } … }]; }

• 通信開始のための手続きがシンプルでいい。 • インスタンス保持しておけば通信をキャンセルすることもできる。

Page 43: さらに上を目指すための iOS アプリ設計

僕はこう思うッス

AFNetworking のようなガッツリした通信ライブラリは本当に必要

か見直してみましょう。 API 仕様の特殊性を吸収する簡単なラッパー

があればほとんどの場合十分。

Page 44: さらに上を目指すための iOS アプリ設計

7. 外部ライブラリの使用を判断するポイント

Page 45: さらに上を目指すための iOS アプリ設計

• まずソースコードは必ず確認する。

• IF仕様がイケてなかったら実装もきっとイケてない(クラッシュの原因にもなりうる)。

• コードが公開されていないものは信頼性が保証されている場合に限定すべき。

• 他にも同様のライブラリがないか確認する。★数や最終コミット日時(継続的にメンテナンスされてるか)なども確認する。

• 分厚いライブラリの場合は警戒する。全面的にその仕様に依存した設計になってしまうのはリスク(例: Three20)。

• 自分が欲しい機能はそのライブラリの何割を占めるか?一部だけならそのコードにインスピレーションを受けた上で自作した方がいいかもしれない。

• それでも使用した方がよければ、ライセンスを確認した上で使わせて頂きましょう。

Page 46: さらに上を目指すための iOS アプリ設計

8. (おまけ)これからの Swift 対応のために

Page 47: さらに上を目指すための iOS アプリ設計

Swift 対応

既存コードを機械的に書き換える Swift 最適化+

こっちの作業は機械がやればいい!

Page 48: さらに上を目指すための iOS アプリ設計

objc2swift project

https://github.com/yahoojapan/objc2swift

Fork me!

Page 49: さらに上を目指すための iOS アプリ設計

総まとめ:僕はこう思うッス

よくできた設計は Storyboard とクラスのインターフェースを一望

するだけで大体の構造が分かる。細かくルールを定めるよりも先に、

どういう設計を目指すのかチームのみんなで合意しておきましょう。

Page 50: さらに上を目指すための iOS アプリ設計

Questions?