追記 2020-12-01
この記事も相当古いものになりましたが、
私自身は現在もMVVMのパターンを利用していますが、VM-Vの間ではFluxパターンを活用しています。
iOSアプリにおけるFluxの難しさと開発を加速させる”store-pattern”
はじめに
22日目担当のmuukiiです。
最近Swiftを2スペで書くことにハマってます😎
先日KickstaterのiOSアプリがオープンソースとして公開されましたね!
GitHub - kickstarter/ios-oss: Kickstarter for iOS. Bring new ideas to life, anywhere.
最初はちら見した程度だったのですが、MVVM + ReactiveCocoaということに気づき興味が湧いたので色々観察していました。
観察した中で良いな!と思ったことが幾つかあったのですが、今回はその一つを共有します。
(私自身はRxSwiftを使用しているため記事の中ではRxSwiftに置き換えてあります。)
ViewModelにprotocolをつかってinputs, outputsを定義している
Kickstarter-iOSでは大体以下のようなテンプレートによりViewModelが定義されていました。
protocol MyViewModelInputs {
}
protocol MyViewModelOutputs {
}
protocol MyViewModelType {
var inputs: MyViewModelInputs { get }
var outputs: MyViewModelOutputs { get }
}
final class MyViewModel: MyViewModelType, MyViewModelInputs, MyViewModelOutputs {
// MARK: - Properties
var inputs: MyViewModelInputs { return self }
var outputs: MyViewModelOutputs { return self }
// MARK: - Initializers
// MARK: - Functions
}
ViewControllerでは以下のような定義でViewModelを扱うようです。
class MyViewController: UIViewController {
let viewModel: MyViewModelType
}
MyViewModelType
にはinputs, outputsしか定義されていないので、
ViewModelへのアクセス時には必ず (Viewからの) 入力なのか出力なのかを選ぶようにさせているのです。
これにどのようなメリットがあるかというと、ViewModelのプロパティ宣言が AnyObserver
or Observable
だけになるのであれば、入力か出力は見てわかります。
ですが、PublishSubject
や Variable
は要件的に使わざるを得ないときはあります。
そのときに入力か出力がわかりにくくなってしまいます。
(プロパティのネーミング次第でどうにかなりそうではありますが。)
そこで、上記のようなViewModelの定義にして、入力か出力をわかりやすくすることができます。
実際にプロパティを宣言した場合以下のようになります。
protocol ProfileViewModelInputs {
var refresh: PublishSubject<Void> { get }
}
protocol ProfileViewModelOutputs {
var name: String { get }
var age: String { get }
var isOnline: Variable<Bool> { get }
}
protocol ProfileViewModelType {
var inputs: ProfileViewModelInputs { get }
var outputs: ProfileViewModelOutputs { get }
}
final class ProfileViewModel: ProfileViewModelType, ProfileViewModelInputs, ProfileViewModelOutputs {
// MARK: - Properties
var inputs: ProfileViewModelInputs { return self }
var outputs: ProfileViewModelOutputs { return self }
let name: String = ...
let age: String = ...
let isOnline: Variable<Bool> = ...
let refresh: PublishSubject<Void> = ...
}
let viewModel: ProfileViewModelType
viewModel.outputs.name ...
viewModel.outputs.age ...
viewModel.outputs.isOnline ...
//======//
viewModel.inputs.refresh.onNext()
結構いい
私はこのアプローチが気に入ったので自分の開発プロジェクトでも導入し始めています。
自然とprotocolが用いられるのでテストを書くときにも良いのではないかと。
いかがでしょうかー?😉
補足ですが protocol extensionを使うと以下のように定義できますが、
inputs
outputs
以降の補完が効かなくなるので現時点ではおすすめしません。
protocol MyViewModelType {
var inputs: MyViewModelInputs { get }
var outputs: MyViewModelOutputs { get }
}
extension MyViewModelType where Self: MyViewModelInputs {
var inputs: MyViewModelInputs { return self }
}
extension MyViewModelType where Self: MyViewModelOutputs {
var outputs: MyViewModelOutputs { return self }
}
毎回書くのが手間なのでスクリプトつくりました。
また、この形式だと毎回書くのがちょっとだけ辛いので、
簡単なシェルスクリプトをひとまず用意しました。
私はAlfredのworkfowに入れて生成するようにしています。
以上です。ありがとうございましたー!