よくこのようなコード目にします:
struct MainView: View {
@State var inputText: String
var body: some View {
VStack {
TextField("Text Input", text: $inputText)
LabelView(outputText: $inputText)
}
}
}
struct LabelView: View {
@Binding var outputText: String // ←この `@Binding var`
var body: some View {
Text(outputText)
}
}
何故このようなコードを書くかと言うと、おそらく多くの人は特に深く考えたことなく、単純に「@State var
の inputText
の値を流し込むから、@Binding var
を条件反射で書いた」的な思考ではないかと思います。
しかし、実は outputText
に関しては、@Binding var
にする必要なく、単純に let
でも問題ないのです。
何故 let
でいけるのかと言うと、@Binding
の役割と、View
が何のかを理解しておく必要があります。
まず @Binding
はどう言う役割かと言うと、これは「この値を変更するので、変更された値を参照元にも反映させてね」と言うことです。つまりどう言うことかと言うと、もし LabelView
において outputText
を変更することがあって、さらにその変更を元の MainView
の inputText
にも反映させたいなら、@Binding
を使う必要があると言うことです。そう、平たく言えば @Binding var outputText
は、単純に @State var inputText
の set
メソッドを暴露してるわけです。SwiftUI の哲学に「Single source of Truth(単一の真実の情報源)」があり、@Binding
は、その「情報源」である @State
への操作の窓口を提供しているだけです。
「でもそしたら、inputText
が変わっても outputText
の描画が更新されないのでは?」
この問題を理解するのは、次の話:View
は何なのか?を理解すればわかるものです。
まず筆者含め、おそらく多くの人が SwiftUI
の View
は class
ではなく struct
であることに驚きを感じたはずかと思います。struct
は値型であり、「オブジェクト」と言う考え方には当てはまらないですし、当然ながらライフサイクルも存在しません。代入操作がある時はすべての値がコピーされ1、var b = a
の代入後に b.p
を変更してもそれが a.p
に反映されることは絶対にあり得ませんし、a
の保持がなくなったら例え b
の保持がまだ残っていても a
のインスタンスが破棄されます。これは今まで参照型の UIView
を使い慣れてきた我々 iOS エンジニアには大きなショックを与えたでしょう、何故なら今まで我々が当たり前のようにやってきた「ビューオブジェクトを参照して描画内容を変更」することが理屈上できなくなったのです。
実際も確かにそれができません。それでも SwiftUI がちゃんと描画できるのは、そもそも View
はビューオブジェクトそのものではなく、ビューの描画レシピです。つまり「ここをこんな風に描画してね」の指示書に過ぎないのです。
そして、SwiftUI は非常に賢く、「この部分の描画に関わる情報が変わったら、この部分を再描画する」ことがわかっているのです。だから @Binding
が全く必要なく、LabelView
の描画に inputText
の値が使われたから、inputText
の値が変わったら LabelView
を再描画する必要があることを、SwiftUI がわかっているのです:
struct MainView: View {
@State var inputText: String
var body: some View {
VStack {
TextField("Text Input", text: $inputText)
LabelView(outputText: inputText) // ←Binding していないので `$` も必要ありません
}
}
}
struct LabelView: View {
let outputText: String // ←ここ `outputText` がそもそも変更することがないので `let` でいけちゃいます
var body: some View {
Text(outputText)
}
}
無駄な @Binding
をやめて、より簡潔で保守しやすいコードを書いていきましょう
-
正確には Swift にはパフォーマンス最適化のために Copy-on-Write と言う仕組みがあって、値型でも代入時に値が即コピーされるわけではなく、変更を加えるときに初めて値がコピーされます。ここはその仕組みではなく、あくまでイメージがしやすいために敢えてこの表現を使いました。 ↩