最近少し入力が多くて画面が多く複雑な画面を実装することになったので、iOSDC2018で発表されてたMicroViewControllerを思い出して、ViewController間のやりとりを入力と出力のプロトコルで縛る方式で行うようにした。
「MicroViewControllerのやり方良いよね」というと、正しく相手に通じないかもしれないので何が良いかというのを書いておこうと思います。私は別にButtonとかCellとかをViewControllerにしたいわけじゃなくて、単に入力と出力のプロトコルが良いよね、コンセンサスとりやすいよね、これをベースとさせてもらって自分で好きにやっていけば良いよねということが私の感想です。
全体的なイメージ
この話のサンプルとして次の4つのViewControllerを考えました
- 何かを編集する目的のある画面のちょっと複雑なEditViewController
- 2パターンの値を編集する画面で2パターン切り替える画面のEditValueContainerViewController
- 値を編集するコンテナの中のEditViewController
- 値を計算したりするCalcViewViewController
入力
public protocol Injectable {
associatedtype Input
func input(_ input: Input)
}
サンプルとしてはEditValueContainerViewController
からEditValueViewController
に初期値を渡すような際にも使える。
class EditValueViewController: UIViewController {
struct Input {
let value: Int
}
...
}
extension EditValueViewController: Injectable {
func input(_ input: Input) {
// self.input = input として使い回すときもあるけどしなかったりもする
if isViewLoaded {
label.text = input.value
}
}
}
func input(_ input: Input)
が単なるメソッドなのが良い。これをvar input: Input
にしてしまうと入力をget
できてしまう。
// プロパティだったらgetできるのでprintしてみる
print(viewController.input)
get
して誰かが困るかっつうと困らない。困らないんだけど入力をget
する意味がないのでできない方が良くて、メソッドなのでセットしかできないのがいい。言い換えると、セットしかしないんだからセッターしかない。良い。
出力
public protocol Interactable {
associatedtype Output
func output(_ handler: ((Output) -> Void)?)
}
例えば、EditValueViewControllerがコンテナに結果を伝えたい
class EditValueViewController: UIViewController {
struct Input {
let value: Int
}
// OutputはInputと同じにもできる。つまり入力の型と出力の型は同じ
typealias Output = Input
private var outputHandler: ((Output) -> Void)?
...
func textFieldが入力されて値が変わったよ(_ changedValue: Int) {
outputHandler?(Output(value: changedValue))
}
}
extension EditValueViewController: Interactable {
func output(_ handler: ((Output) -> Void)?) {
outputHandler = handler
}
}
これも入力と同じで出力がメソッドなのがいい。
その他の良い点
その他の良い点も書いておく。
- そもそもこのprotocolはViewControllerに限定せずViewでも使えるのがいい
- 複数人でも入力と出力だけやり方を揃えてぶらさず、そこを重点的にチェックすれば良くなるはず
- また、入力と出力さえ公開されていればよくて他はすべて
private
でいい。人のコードをチェックする際に考えることが減りそうなのもいい