以下のブログ記事の翻訳です1。
Dependency Injection in MVVM Architecture with ReactiveCocoa Part 2: Project Setup
前回のブログ記事では、MVVM (Model-View-ViewModel) とReactiveCocoaの基本的な概念を説明しました。今回の記事からは、実際にMVVMアーキテクチャのアプリを開発し、Swinjectを用いたdependency injectionを行っていきます。MVVMの各コンポーネント間で受け渡されるイベントを扱うためにReactiveCocoaを利用します。このブログ記事では、Model、View、ViewModelをフレームワークとして構成するXcodeプロジェクトの設定方法を学びます。
例題のアプリは、以下のGIFアニメのように、PixabayのAPIを通して非同期で画像を検索・ダウンロード・表示します。ソースコードはGitHubのリポジトリからダウンロードできます。
- SwinjectMVVMExample: 発展形を含むプロジェクト
- SwinjectMVVMExample_ForBlog: (Xcodeや外部フレームワークの更新を除き) ブログ記事に沿った説明のための簡略化したプロジェクト
要求事項
Carthageはインストーラ (Carthage.pkg)でインストールできます。もしCarthageを使うのが初めてであればこちらのチュートリアルを参考にしてください。
PixabayのAPI keyは無料で入手できます。Pixabayでアカウントを作成してログインしてから、APIドキュメントのページにアクセスしてください。あなた固有のAPI keyが "Request parameters" のセクションに表示されます。
プロジェクトの設定の概要
前回の記事で、MVVMアーキテクチャではViewがViewModelに依存し、ViewModelがModelに依存することを学びましたが、実際にModel、View、ViewModelを表すプロトコル、クラス、構造体を作るときにどのようにすれば依存性の方向を強制することができるでしょうか?もしすべての型がひとつのディレクトリの下、あるいはアプリケーションターゲットの下であっても、ひとつの場所に置かれるとすぐにカオスになってしまいます。
Javaや.NETでは、複数のJARやDLLファイルでアプリケーションを構成し、各コンポーネントの責任を明確にすることがよく行われます。iOS 8でダイナミックフレームワークが導入されたので、iOSアプリでも複数のダイナミックフレームワークから構成されるように設定するこることが簡単になりました。以下の図のように、Model、View、ViewModelのフレームワークからなるiOSアプリのアーキテクチャは、依存性の方向をViewからViewModelへ、ViewModelからModelへと強制することができ、アプリケーションがそれらの依存性を注入するようにできます。たとえば、ある型をViewModelフレームワークに定義したら、その型からModelフレームワークにある型を参照することはできますが、Viewフレームワークにある型を参照することはできません。
このアーキテクチャにより依存性の向きの一貫性を保つことができ、アプリの開発・テスト・メンテナンスが容易になります。
MVVMのフレームワークからなるプロジェクトの設定
それでは、MVVMフレームワークで構成されるXcodeプロジェクトを作成していきましょう。File > New > Project...
メニューを選択し、iOS > Application > Single View Application
の項目を選択してください。Product NameをSwinjectMVVMExample
にし、.SwinjectMVVMExample
をBundle Identifierの末尾に追加してください。LanguageをSwiftにし、DevicesをiPhoneにします。Include Unit Tests
だけチェックをつけてください2。その後、どこかローカルな場所にプロジェクトを保存してください。
次に、Model、View、ViewModelフレームワークを追加していきます。今回の例題アプリでは、それぞれExampleModel
、ExampleView
、ExampleViewModel
という名前にします。もしあなた自身のアプリを作成する場合、あなたのアプリ名
+ Model
、View
、ViewModel
と命名することがお薦めです。たとえば、Foobook
という名前のアプリであれば、FoobookModel
、FoobookView
、FoobookViewModel
という名前にします。
File > New > Target...
メニューを選択し、iOS > Framework & Library > Cocoa Touch Framework
を選択し、Next
ボタンを押してください。次のページでProduct NameをExampleModel
に、LanguageをSwiftに設定します。Include Unit Test
はチェックされた状態にしてFinish
をクリックしてください。同様にしてExampleViewModel
とExampleView
のフレームワークターゲットを追加してください。
Project Navigator (Xcodeの左側にあるペイン) のSwinjectMVVMExample
を右クリックし、New Group
を選択してください。グループ名をTests
にします。Project NavigatorでExampleModel
、ExampleViewModel
、ExampleView
、ExampleModelTests
、ExampleViewModelTests
、ExampleViewTests
、SwinjectMVVMExampleTests
をドラッグして、下の画像のように整理してください。Project NavigatorでSwinjectMVVMExample
を選択し、画像のようにターゲットの順番を並び替えてください。
ターゲットのDependencyとLinkの設定をしていきます。Project NavigatorでSwinjectMVVMExample
を選択し、TARGETSからExampleViewModel
を選択してください。Build PhasesタブのTarget Dependenciesの下にある+
ボタンをクリックし、ExampleModel
を追加してください。Link Binary with Librariesの下にある+
ボタンをクリックし、ExampleModel.framework
を追加してください。同様にして、ExampleViewModel
とExampleViewModel.framework
をExampleView
ターゲットの各所に追加します。ExampleModel
とSwinjectMVVMExample
のターゲット設定はそのままにします。ExampleModel
は依存性がなく、SwinjectMVVMExample
はデフォルトで設定が追加されるためです。
ユニットテストのターゲット設定も追加していきます。Project NavigatorでSwinjectMVVMExample
を選択し、TARGETSからExampleModelTests
を選択します。GeneralタブでHost ApplicationをNone
に設定します。同様にしてExampleViewModelTests
とExampleViewTests
のHost ApplicationをNone
にします。
再度TARGETSからExampleModelTests
を選択してください。Build PhasesタブでTarget DependenciesからSwinjectMVVMExample
を削除してください。その項目の選択後に-
ボタンをクリックすれば削除できます。同様にして、ExampleViewModelTests
ターゲットとExampleViewTests
ターゲットからSwinjectMVVMExample
のTarget Dependencyを削除します。
TARGETSからExampleViewModelTests
を選択してください。Build Phases
タブでLink Binary with LibrariesにExampleModel.framework
を追加してください。同様にして、ExampleViewTests
の同じ場所にExampleViewModel.framework
を追加してください。リンク先のフレームワークに存在する型のスタブやモックを作成するため、これらのリンク設定が必要になります。
ビルドスキームも設定しましょう。Xcodeのツールバーにあるスキームのボタンをクリックし、Manage Schemes...
を選択します。もしExampleModel
、ExampleViewModel
、ExampleView
という名前のスキームがあれば、それらを選択して-
ボタンをクリックし削除します。その後、SwinjectMVVMExample
スキームを選択し、Edit
ボタンをクリックします。BuildとTestの設定で、ExampleModelTests
、ExampleViewModelTests
、ExampleViewTests
、SwinjectMVVMExampleTests
がチェックされていることを確認してください。
これでプロジェクト設定が完了しました。Command-R
でアプリを実行し、その後Command-U
でユニットテストを実行してください。プロジェクトが正しく設定されたか確認できます。もしUmbrella Headerに関するエラーが出たら、以下の画像のように、そのヘッダーファイルのTarget Membershipを修正し、アクセシビリティをPublic
に設定してください。XcodeはたまにUmbrella HeaderのTarget Membershipを間違えて設定してしまうことがあります。
Carthageによる外部フレームワークのインストール
今回の例題アプリでは、ReactiveCocoa, Himotoki, Alamofire, Swinject, Quick, Nimbleをインストールします。Alamofire, Quick, Nimbleは前回の例題アプリで使用しましたね。今回は型安全なJSONデコードライブラリであるHimotokiをSwiftyJSONの代わりに使用します。Himotokiの詳細については、次回の記事で実際に使用するときに説明します。
上記のフレームワークをCarthageでインストールするため、以下の内容でCartfile
という名前のテキストファイルを作成してください。
Cartfile
github "ReactiveCocoa/ReactiveCocoa" "v4.0.0-RC.1"
github "ikesyo/Himotoki" ~> 1.3.0
github "Alamofire/Alamofire" ~> 3.1.2
github "Swinject/Swinject" ~> 1.0.0
github "Quick/Quick" == 0.8.0
github "Quick/Nimble" == 3.0.0
Terminalでcarthage update --no-use-binaries
を実行してください3。Carthageがフレームワークのビルドを完了するまで数分 (あるいはしばらく) 待ちます4。もしGitを使用しているなら、Carthageがビルドしたフレームワークを除外するように設定したSwift用の.gitignore
がここにあります。
ビルドが完了したらProject NavigatorのSwinjectMVVMExample
を右クリックし、New Group
を選択してください。新しいGroupをFrameworks
という名前にします。Carthageがビルドしたフレームワークを、FinderのCarthage/Build/iOS
ディレクトリからXcodeのFrameworks
グループまでドラッグ&ドロップしてください。追加するターゲットを選択するシートが表示されたら、すべてのターゲットのチェックを外してFinish
ボタンをクリックします。
Project NavigatorでSwinjectMVVMExample
を選択し、TARGETSからExampleModel
を選択してBuild Phasesタブを開いてください。Frameworks
グループにあるAlamofire.framework
, Himotoki.framework
, ReactiveCocoa.framework
, Result.framework
5をBuild PhasesタブにあるLink Binary with Librariesまでドラッグ&ドロップしてください。同様にして、ExampleViewModel
とExampleView
の同じ場所にReactiveCocoa.framework
とResult.framework
を追加してください。SwinjectMVVMExample
の同じ場所にはSwinject.framework
を追加してください。ExampleModelTests
, ExampleViewModelTests
, ExampleViewTests
の同じ場所にReactiveCocoa.framework
, Result.framework
, Quick.framework
, Nimble.framework
を追加してください。SwinjectMVVMExampleTests
の同じ場所にはSwinject.framework
, Quick.framework
, Nimble.framework
を追加してください。AlamofireとHimotokiはModelのみで使用します。Swinjectはアプリケーションとそのユニットテストのみで使用し、QuickとNimbleはユニットテストのみで使用します。
TARGETSでExampleModelTests
を選択し、Build Phasesタブの下の+
ボタンをクリックし、New Copy Files Phase
を選択してください。そのPhaseをCopy Frameworks
にリネームします。そのPhaseの中で、DestinationをFrameworks
に設定してください。Project NavigatorのFrameworks
にあるAlamofire.framework
, Himotoki.framework
, ReactiveCocoa.framework
, Result.framework
, Quick.framework
, Nimble.framework
をCopy Frameworks
Phaseにドラッグ&ドロップして追加します。同様にExampleViewModelTests
とExampleViewTests
にCopy Frameworks
Phaseを追加し、上記6つのフレームワークをそのPhaseにドラッグ&ドロップしてください。同様にSwinjectMVVMExampleTests
にCopy Frameworks
Phaseを追加し、そのPhaseにQuick.framework
とNimble.framework
を登録してください。SwinjectMVVMExampleTests
はアプリにホストされるので、アプリに入っていないQuickとNimbleだけCopy Frameworks
Phaseに追加しています。
最後の設定はCarthageのREADMEに書かれているとおりです。Project NavigatorでSwinjectMVVMExample
を選択し、TARGETSからSwinjectMVVMExample
を選択してBuild Phasesタブを開いてください。そのタブの下の+
ボタンをクリックし、New Run Script Phase
を選択してください。そのPhaseをRun Script for Frameworks by Carthage
にリネームし、スクリプトを書くテキストフィールドに以下の行を追加してください。
/usr/local/bin/carthage copy-frameworks
その後、Input Filesの+
ボタンを押して以下のパスを追加してください。
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
$(SRCROOT)/Carthage/Build/iOS/Himotoki.framework
$(SRCROOT)/Carthage/Build/iOS/ReactiveCocoa.framework
$(SRCROOT)/Carthage/Build/iOS/Result.framework
$(SRCROOT)/Carthage/Build/iOS/Swinject.framework
もう一度Build Phasesタブの下の+
ボタンをクリックし、今度はNew Copy Files Phase
を選択してください。そのPhaseの名前はCopy dSYMs
とします。そのDestinationをProducts Directory
に変更します。FinderのCarthage/Build/iOS/
ディレクトリからXcodeのCopy dSYMs
PhaseにAlamofire.framework.dSYM
, Himotoki.framework.dSYM
, ReactiveCocoa.framework.dSYM
, Result.framework.dSYM
, Swinject.framework.dSYM
をドラッグ&ドロップしてください。Project NavigatorのSwinjectMVVMExample
を右クリックし、New Group
を選択してください。その名前をdSYMs
とし、Project Navigatorに自動的に追加されたdSYMファイルをdSYMs
グループに入れて整理してください。
おめでとうございます!プロジェクトの設定が完了しました。試しにCommand-R
とCommand-U
でアプリとユニットテストを実行し、問題なく設定ができているか確認してください。もし何かエラーが出たら、GitHub上にあるプロジェクトをダウンロードして、プロジェクトの設定を比較して原因を探してみましょう。
まとめ
Model、View、ViewModelをそれぞれフレームワークとして構成したアプリのアーキテクチャにより、依存性の方向が一貫してViewからViewModel、ViewModelらかModelとなるように保証できることがわかりました。実際にMVVMアーキテクチャのXcodeプロジェクトを作成し、外部フレームワークをCarthageでインストールしました。次回のブログ記事からは、MVVMアーキテクチャを活かし、ReactiveCocoaとSwinjectを用いて例題のアプリを開発していきます。
もし質問、提案、問題などがあれば気軽にコメントをどうぞ。
-
訳注: 英語版の著者本人による翻訳のため、翻訳に関わる著作権上の問題はありません。 ↩
-
本記事の対象外のため、UI testsは除外しています。 ↩
-
古いベータ版のXcodeでビルドされたフレームワーク (zipファイル) をダウンロードしないよう、
--no-use-binaries
オプションをcarthage update
コマンドに与えています。使用しているXcodeのバージョンがバイナリを作成したXcodeのバージョンに適合する場合、オプションなしでcarthage update
を実行するだけで済みます。 ↩ -
carthage update --no-use-binaries
コマンドでエラーが発生したら--verbose
オプションも付けて再度コマンドを実行し、問題を調査してみてください。 ↩ -
Result.framework
はReactiveCocoaが使用します。 ↩