Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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 だけになるのであれば、入力か出力は見てわかります。
ですが、PublishSubjectVariable は要件的に使わざるを得ないときはあります。
そのときに入力か出力がわかりにくくなってしまいます。
(プロパティのネーミング次第でどうにかなりそうではありますが。)

そこで、上記のような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に入れて生成するようにしています。

gen_viewmodel.sh · GitHub

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

muukii
iOSアプリ開発してます。
https://muukii.app
eure
オンラインデーティングサービス「Pairs」の運営・開発をしている企業。様々なモダンな技術を駆使してビジネスを成長させています。
https://eure.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした