MVVM
View
UI
UIButton, UITextField, UITableView, UICollectionView, ...
Model
データ
"your data" の抽象的表現
POJO (Plain Old Java Object), POCO (Plain Old Class Object) などと行ったりする
CLLocationManager, Alamofire, Facebook SDK, Database like Realm, Service Class ...
ViewModel
View と Model の間に位置するレイヤー
データの 準備 と 操作
ViewModel 自身がデータの 変更 はしない
サービスクラスとやり取りする。ある時点のデータを表現するので、class ではなく struct にする
View と Model はわかりやすいが、ViewModel という概念がとてもわかりづらい。ViewModel は、ある時点のアプリケーションの状態 であり、View -> Model, Model -> View の双方向の呼び出しを管理する。
code:LoginViewController.swift
class LoginViewController : UIViewController {
// View
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var confirmButton: UIButton!
// ViewModel
var viewModel = LoginViewModel()
override func viewDidLoad(){
super.viewDidLoad()
// View と ViewModel の繋ぎ方は色々あって難しい
// イベントハンドラを登録する
// これだと、View -> ViewModel はできるが、逆ができない (例えば、バリデーション結果をViewModelから返す
// この逆をやるのがとても難しく、色々なやり方がある
usernameTextField.addTarget(self, action:
forControlEvents: UIControlEvents.EditingChanged)
passwordTextField.addTarget(self,
forControlEvents: UIControlEvents.EditingChanged)
}
func usernameTextFieldDidChange(textField: UITextField){
viewModel.username = textField.text ?? ""
}
func passworldTextFieldDidChange(textField: UITextField){
viewModel.password = textField.text ?? ""
}
func confirmButtonTapped(sender: UIButton){
viewModel.attemptLogin()
}
}
code:LoginViewModel.swift
struct LoginViewModel {
var username: String = ""
var password: String = ""
func attemptToLogin() {
let params = [
"username": username,
"password": password
]
ApiClient.shared.login(email: email, password: password) { (response, error) in
}
}
}
RxSwift は、あらゆるイベントやイベント操作が、配列に対する操作に抽象化されている
アーキテクチャにおいて重要なのは、関心事の分離ももちろんだが、分離されたモジュール間がどのように連結されているか?がかなり重要で、特に非同期や複雑な状態遷移になると、単純には行かなくなってくる。
メソッド呼び出し
MyClass -> OtherClass。単純。MyClass が OtherClass を参照する。
code:swift
class MyClass {
var other = OtherClass()
func call() {
other.call();
}
}
DIP
依存関係逆転の原則。実質的な依存は同じだが、protocol あるいは interface を参照させる。このような中間層を挟む利点は、コードの依存関係を制御できること。変化しやすい具象ではなく、安定した抽象に依存させる。抽象から先がいくら変更されても参照元には影響がない、という変更容易性が利点。これによってモジュールを Pluggable にすることもできる。
code:swift
protocol OtherProtocol {}
class MyClass {
var other: OtherProtocol = ... // ここには DI で実装を注入する
func call() {
other.call()
}
}
Observer
Subject に Observer を登録し、Subject が Observer に一斉に通知を行う。Subject と Observer の間は抽象を挟むので DIP に沿っている。
code:swift
protocol Observer {
func update()
}
class Subject {
var observers: Observer[] = []
func addObserver(obs: Observer) {
self.observers.append(obs)
}
func notify() {
for observer in observers {
observer.update()
}
}
}
code:swift
// コールバックを登録し、それを後から呼び出してもらうのも、Observer と似ている
// Subject 側が呼び出すのが、Observer という抽象なのか、コールバック関数なのか?の違い
// 何れの場合でも、呼び出す側 (Subject) にとって呼び出される側 (Observer) が抽象的な表現になっている
//
// 注入のされかたも違う。Observer の場合自信を登録、Callback の場合代入、DI する場合もあるかもしれない
class ViewModel {
var isValid: Bool = "" {
didSet {
isValidCallback?(isValid: isValid)
}
}
var isValidCallback: ((isValid: Bool) -> Void)?
}
RxSwift は、イベントの lodash, あるいはアンダースコア
全てのイベントの変更が、配列の動きに抽象化されている?
モジュールが呼び出し先を、どう参照するか?どのような表現で参照するか?
純粋な参照, protocol/interface, callback, ...
モジュールに呼び出し先を、どのように注入するか?
代入, 専用メソッド, DI, ...
複数の View の共通状態を更新するたびに、各 View に更新を明示的に支持する
メリット: 明示的に同期するのでコード上で追いかけやすい
デメリット: View がたくさんあると View 同士の相互依存度が高まり、コードが煩雑になる
複数の View が単一の Model を Observe し、Model が更新された時に全ての View が同期して更新されるパターン
メリット: コードがシンプルになる
デメリット: コード上から流れが追いづらい
Reactive Extension で頑張らない MVVM もあるようだ
Data binding
何で実現するか?
自作
property observers