Kickstarter-iOSのViewModelの作り方がウマかった

## 追記 2020-12-01

この記事も相当古いものになりましたが、
私自身は現在もMVVMのパターンを利用していますが、VM-Vの間ではFluxパターンを活用しています。

[iOSアプリにおけるFluxの難しさと開発を加速させる”store-pattern”](https://medium.com/eureka-engineering/flux-store-pattern-verge-2020-76781fa429aa)

## はじめに

22日目担当のmuukiiです。
最近Swiftを2スペで書くことにハマってます😎

先日KickstaterのiOSアプリがオープンソースとして公開されましたね！
[GitHub - kickstarter/ios-oss: Kickstarter for iOS. Bring new ideas to life, anywhere.](https://github.com/kickstarter/ios-oss)

最初はちら見した程度だったのですが、MVVM + ReactiveCocoaということに気づき興味が湧いたので色々観察していました。

観察した中で良いな！と思ったことが幾つかあったのですが、今回はその一つを共有します。

(私自身はRxSwiftを使用しているため記事の中ではRxSwiftに置き換えてあります。)

## ViewModelにprotocolをつかってinputs, outputsを定義している

Kickstarter-iOSでは大体以下のようなテンプレートによりViewModelが定義されていました。

```swift
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を扱うようです。

```swift
class MyViewController: UIViewController {
  let viewModel: MyViewModelType
}
```

`MyViewModelType` にはinputs, outputsしか定義されていないので、
ViewModelへのアクセス時には必ず (Viewからの) 入力なのか出力なのかを選ぶようにさせているのです。

これにどのようなメリットがあるかというと、ViewModelのプロパティ宣言が `AnyObserver` or `Observable` だけになるのであれば、入力か出力は見てわかります。
ですが、`PublishSubject` や `Variable` は要件的に使わざるを得ないときはあります。
そのときに入力か出力がわかりにくくなってしまいます。
(プロパティのネーミング次第でどうにかなりそうではありますが。)

そこで、上記のようなViewModelの定義にして、入力か出力をわかりやすくすることができます。

実際にプロパティを宣言した場合以下のようになります。

```swift

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> = ...
}
```

```swift
let viewModel: ProfileViewModelType

viewModel.outputs.name ...
viewModel.outputs.age ...
viewModel.outputs.isOnline ...

//======//

viewModel.inputs.refresh.onNext()
```

## 結構いい
私はこのアプローチが気に入ったので自分の開発プロジェクトでも導入し始めています。
自然とprotocolが用いられるのでテストを書くときにも良いのではないかと。

いかがでしょうかー?😉

補足ですが protocol extensionを使うと以下のように定義できますが、
`inputs` `outputs` 以降の補完が効かなくなるので現時点ではおすすめしません。

```swift
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に入れて生成するようにしています。

[gen_viewmodel.sh · GitHub](https://gist.github.com/muukii/0c5c5e83d0d257fd5a7a18f9486a476b)


以上です。ありがとうございましたー！