iOS/Androidアプリ開発の
Good Practice
2015年7月
ゆめみ 森下
1
はじめに
• ゆめみ内でのiOS/Androidアプリ開発におけるPractice(実践・慣習)を
まとめたものです
• 書きかけなので随時更新していきます
2
用語の整理
基本的な単語や、ゆめみ内で使う特殊な単語の定義もあるので整理しておきます
3
プラットフォーム別の用語とこの資料の表記
iOSのclass,	
  protocol Androidのclass,	
  interface この資料の表記
AppDelegate/UIApplication Application Application
-­‐ Activity Activity
UIViewController Fragment ViewController	
  (略してVC)
NSUserDefaults Preference Preference
4
同期/非同期 な呼び出し-­‐リターン
• 「同期的な呼び出し-­‐リターン」
• 呼び出し終了時に戻り値が戻ってくるタイプ
• 構造上同期的であることが強制されている
• 「非同期的な呼び出し-­‐リターン/通知」
• callbackや通知などで制御が戻ってくるタイプ
• その制御がいつ戻ってくるかは実はわからない
• 呼び出し終了前に呼ばれるケースも有り得ることには注意
5
非同期の「Callback的」「通知的」 の違い
6
非同期の「Callback的」「通知的」 の違い
7
非同期の「Callback的」「通知的」 の違い
• 「通知的」な方には、 add/remove	
  というような
明示的な通知の「受け取り開始」「受け取り解除」がある
• 「callback的」な方では、「解除」は難しい
• ライフサイクルが短いInstanceは「通知」を使うのが基本になる
• そうしないと、自分が無効な状態なのにcallbackを受け取ってしまう
• 例)	
  Fragment-­‐>Model,	
  ViewController-­‐>Model
• ライフサイクルが必ず長いInstanceはCallbackでもOK
• 例) Model	
  -­‐>	
  API,	
  Service	
  -­‐>	
  API,	
  Model	
  -­‐>	
  Data系
8
必須Practice
9
必須Practice
• Gitによるバージョン管理
• Crashレポートツールの導入
• Debug/Release や 環境別のビルドの切換をコードを書き換えないで
行うこと
10
Gitによるバージョン管理
• ソースコードのバージョン管理にはGitを使っています
• ブランチの切りやすさや、大規模でも高速である、など利点が大きい
• 基本ルール
• 少なくともリリースしたプロダクトは過去の任意のバージョンを再現可能なこと
• 自動生成されるものはCommitしない
• .gitignore をちゃんと設定する
• *.class や ビルドされたバイナリ等
• ビルドに必要な画像はOK、動画は・・・仕方ないときもある
• Androidだと R.java などもコミット不要
• Git-­‐Flow的なブランチ運用にする
• master:	
   リリースしたソースコードのブランチ (リリースしたらTAGを打つこと)
• develop:	
  	
  次期リリースするソースコードが合流するブランチ
• feature/*:	
  特定の機能開発ブランチ。コミット単位でなるべく意味を持たせること。
11
Crashレポートツールの導入
• リリース後の不具合を早急に発見するために必ず導入しましょう
• ゆめみでは Crashlytics を標準的に使っています
• https://try.crashlytics.com/
12
Debug/Release や 環境別のビルドの切換を
コードを書き換えないで行うこと
• 例えば、通信先host名,	
  API	
  Key,	
  Log出力などの切換です
■標準的な実現方法
• Android
• Gradle+BuildConfig+(環境変数)
• iOS
• #ifdef DEBUG + #defineなどのプリプロセッサ+定数定義
• 場合によっては、 *.h	
  ファイルの動的な生成
13
推奨Practice
14
推奨Practice
• 内部の基本アーキテクチャはMVPパターンに近いものにする
• アプリケーションのアーキテクチャは徐々に発展させていく
• 理想の開発に占める労力の割合は「View 80%」「その他 20%」
• 定数は「環境情報」「Configuration情報」「内部定数」に分ける
15
ViewController
内部の基本アーキテクチャはMVPパターン
Application
Activity
View
View
Entity
Model	
  Container
(for	
  DI)
APIPreference
ViewController
Model
DB/NoSQL
プレゼンテーション層
ビジネス層
データ層
PresenterVCとPresenterはわ
けない場合もある
StateMachine
StateMachine
通知的IF
File/Cache
set
複数のVCに跨る時がある
各種Container
(for	
  DI)
16
MVPパターン?
• MVCパターンみたいなものだけど、ViewがModelを直接参照しない
で、PresenterがModelの値をViewにセットする(ようなイメージ)
• http://tech.recruit-­‐mp.co.jp/mobile/android-­‐architecture/
• 普通はViewがModelを直接参照しないので、この形式といえばこの
形式になっているはず
• 大事なこと
• 「Model」が存在すること
• 「ViewController」は肥大化するので、Presenterの機能を分離する設計があ
るということを知っておくこと
17
MVPパターン、と言っても全体構成としては
色々あります
http://tech.recruit-­‐mp.co.jp/mobile/android-­‐architecture/ より
18
Modelってなんだ?
• とりあえずこれを読んでみて欲しい
• 「iOS/Androidアプリエンジニアが理解すべきModelの振る舞い」
• http://www.slideshare.net/mokemokechicken/iosandroidmodel
• 「プレゼンテーション層」から見れば、
• データのCRUDや処理を依頼できる「サーバ」みたいなレイヤー
• 非同期のリターンは「通知」でもらう
• Modelは非常に長命でContainerのようなDI機能からもらうイメージ
• データや状態は公開されているが、カプセル化・抽象化されている
• 安全な操作、状態の矛盾を防げるようにする
19※実際の名前はどうでも良い
具体例
• Presentation層のAPI	
  callback実装 →	
  Model通知実装 への書き換え
• Android
• https://github.com/mokemokechicken/android_refactor_training/blob/master/doc/exa
mple_1.md
20
アプリケーションのアーキテク
チャは徐々に発展させていく
21
アプリケーションのアーキテクチャは徐々に
発展させていく
• アプリ開発においては次のようなライフサイクルを持つことが多い
• 最初はViewだけのMockを作る
• 次第に通信部分と連携するようになる
• 徐々に仕様変更(View遷移、通信周り、キャッシュ周り)や微調整を行う
22
最初はViewだけのMockを作る
ViewController
Application
Activity
View
ViewViewControllerプレゼンテーション層
+(ダミーデータ)
+(なんちゃってロジック)
この時は、ビジネス層なんて要らない
コアな画面を1つ2つちゃんと作って、
他はパワポの画像を貼っておく、とかで良い
標準パーツとか使わない方が
完成品とのイメージの差がなくてむしろ良い
機能ではなく、見た目のゴールを再現する
空いた時間でアーキテクチャの基本を試行錯誤しておく
アーキテクチャのプロトタイプを内部で考える
23
次第に通信部分と連携するようになる
ViewController
Application
Activity
View
ViewViewControllerプレゼンテーション層
+(ダミーデータ)
+(なんちゃってロジック)
API通信機能
データ共有機能 データ操作機能
このまま拡張してはダメ、絶対。
ここで建て付けは整理すること。
☓ ☓
☓
ビジネスロジック☓
が、ここで注意
24
次第に通信部分と連携するようになる
ViewController
Application
Activity
View
View
Entity
Model	
  Container
(for	
  DI)
API
ViewController
Model
プレゼンテーション層
ビジネス層
データ層
通知的IF
+(なんちゃってロジック)
API通信機能
データ共有・操作機能
+(ダミーデータ)
VCをなるべく軽くする
ダミーなどは残ることもある
機能単位にModelを作る
set
ビジネスロジック
25
徐々に仕様変更や微調整を行う
• 最初に(自分たちが)描いた図に近づいていくことになる
• 「全部盛り」になったときのクラス構成をイメージして、開発者同士が共有
しておく
• そうすれば「もうここは次の設計ステージに移行した方がいいね」と一言で通じる
• そうでないとなかなか議論がまとまらない
• この辺りからReviewしたコードのみMerge OK、などのルールにすると良い
かもしれない
• 最初からそうでも良いですが
• 人員に余裕がないと辛いですが
26
理想の開発に占める労力の割合
27
ViewController
理想の開発に占める労力の割合
Application
Activity
View
View
Entity
Model	
  Container
(for	
  DI)
APIPreference
ViewController
Model
DB/NoSQL
プレゼンテーション層
ビジネス層
データ層
PresenterVCとPresenterはわ
けない場合もある
StateMachine
StateMachine
通知的IF
File/Cache
set
80%
20%
アピールポイント
パターン化が難しい
よく変更がある
自動化やパターン化容易
決まれば変更は少ない
28
• 危険信号
• 簡単な遷移やUIの変更が難しい
• WiFiだと快適に動くのに3G回線だとすぐ固まる、落ちる
• 画面がちらちらする。レイアウトが乱れている
• よくある原因
• 画面ベースで開発しているため、画面の遷移や構成要素が変わると対応できない
• 開発時はWifiで無計画に作っているので、非同期の問題に気がつかない
• 要件や仕様を最低限満たすことに集中していて画面を見ていない。システム都合
で画面がおまけになっている
• 解決する3つの手順
• 通信と表示制御を分離して整理する
• これによりシステム都合で画面が制限されることが少なくなる
• 画面単位でファイルを作るのではなく、機能単位になるように切り口を変え、パーツ
に分解する
• 隠れフラグ(static変数類、ローカルストレージ、ViewのIDや状態)を排除して、状態
管理をまとめる
29
定数の管理
30
定数は「環境情報」「Configuration情報」「内
部定数」に分ける
• 環境情報: デプロイ先などで変わる値
• dev1,	
  staging1,	
  staging2…,	
  production	
  などの環境
• 通信先URLなどのEndpoint
• ID/PASS,	
  SNSや外部サービスのAPI-­‐Key
• 署名の鍵
• Configuration情報: Release/Debugなどで変わる値
• Log Levelなどの値
• Debug機能のOn/Offなど
• 内部の定数: 単純に内部で使っている定数
31
環境情報
• 環境情報はビルドやデプロイのタイミングで注入できるのがベスト
• 事前に数種類(dev用,	
  内部staging用,	
  staging用,	
  production用)定義してお
いて、ビルド時に選択できるというのでも良い
• Android
• Gradle+	
  buildConfigField (+	
  環境変数)などが便利
• iOS
• config_dev.h,	
  config_production.h などを事前に定義して、ifdefでimportを分ける
• もしくは、 ビルド時に動的に.hを生成する
32
Configuration情報
• 下記のような仕組みをまとめられるようなものを準備する
• Android
• 例えば、BuildConfig.DEBUGによって異なる値が返るような何かを作る
• iOS
• 例えば、#ifdef DEBUG などで異なる値が定義・returnされる何かを作る
33
内部定数
• Android
• static	
  public	
  final	
  で定数にするか、 getterで提供する
• iOS
• #define	
  などで定義する
34
具体例
• 環境情報・Configuration情報の書き方
• Android:	
  
https://github.com/mokemokechicken/android_refactor_training/blob/maste
r/doc/example_2.md
35
実装が難しいので注意する点
36
アプリケーションの起動時の処理
37
アプリの起動シーケンスは複雑になりやすい。
「初回起動時(ID取得・PushToken取得)」「2回目起動時」
「Homeから復帰」「Push通知やIntent/URL	
  Schemaからの起動」
での分岐や、
初期データを入力しているか(誕生日とか)どうかで画面を分岐
データがDLされているか分岐
などがあり得る。
これらが非同期での処理になるので、実装が少しむずかしい。
→	
  StateMachineを使うとかなり改善する
→	
  気合で実装することもあるが、このロジックをActivityに持たせない方が良い
ViewControllerの状態は複雑になりやすい
38
一つの画面(ViewController/Presenter)で
多数の非同期イベント(通信、UIイベント、Observeイベント)を制御するので。
→	
  これもStateMachineを使うと簡潔になる
アンチパターン
よく見かけるものたち・・・
39
「Application/Activity/Fragment」や「AppDelegate/ViewController」に
他のObjectから参照されるような値・状態を持っている。
• static	
  publicな何かになることが多い
• ViewControllerをsingletonにしてしまう人もいる
■ アンチパターン: 「いつも便利Global変数」
■ 問題点
■ 解決方法
何がいつ更新するかが発散していくので、保守性が著しく下がる。
厳密な状態管理が難しいので、不整合が起こりやすい。
ViewControllerはsingletonにしてはいけない!
「Model」を作る
Viewの状態を共有したいなら、状態を「Presenter」に管理させ、Presenterを共有させる
40
Application,	
  BaseActivity,	
  BaseViewControllerがどんどん多機能になっていく。
通信機能やデータ保存機能などが実装されている。
■ アンチパターン: 「万能Baseクラス」「Frameworkクラス肥大化」
■ 問題点
■ 解決方法
ActivityやViewControllerは、そういう役割を持つべきではない。
通信機能を利用するために、 他のObjectから ViewControllerの参照が欲しくなり、
依存関係が破綻していく
余分な機能をApplicationやVCに持たせない。
他のレイヤーのClass(Model,	
  API,	
  DB)に実装する。
41
1Methodが200行を超える。
(2000行を超えるものを見たことがある・・・)
■ アンチパターン: 「大作書きおろしメソッド」
■ 問題点
■ 解決方法
非常に可読性や保守性が下がる。
単体テストをまず書くことができない。
設計を見なおして、分割する。
42
同じ switch	
  (mode/type/state) {	
  
case	
  ...	
  
} が至る所にできる
■ アンチパターン: 「増殖switch」
■ 問題点
■ 解決方法
可読性、保守性がすごい速度で下がっていく。
mode/type/stateの追加・変更が難しい。
全てを把握しないとコードが読めなくなる。
「いつも便利Global変数」と合わさると絶大な破壊力になる。
modeやtypeなら:	
  Object指向的な解法
stateなら: StateMachineの導入
43
Activityがたくさんある
Activityが消えることを考慮していない
■ Androidアンチパターン
■ 問題点
■ 解決方法
不具合が起こりやすい。
画面のViewはFragmentで管理する。(画面毎にActivityを作らない)。
端末のDeveloper Modeで Homeボタンを押したら
Activityが必ず消えるOptionをOnに設定して開発する。
44
他:警告・チェックポイント
45
警告・チェックポイント
• Viewの状態で、Presenterなどの処理がわかれている
• If	
  (view.isEnable())	
  とか…
• ViewController,	
  Presenter から直接APIやPreferenceを参照している
• 設計思想に反している
• GAタグなどで(遷移元、遷移先)を記録するのは大変である
• 仕様を調整してもらって、GAタグ(表示画面) にしてもらうのが吉
• そうでない場合は、かなり大きくその工数を見積もる必要がある
※良い方法があると良いですが
46

iOSやAndroidアプリ開発のGoodPractice