以下のブログ記事の翻訳です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.framework5を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が使用します。 ↩


























