21
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

その `@Binding var` 本当に必要?

Posted at

よくこのようなコード目にします:

MainView.swift
struct MainView: View {
    
    @State var inputText: String
    
    var body: some View {
        
        VStack {
            TextField("Text Input", text: $inputText)
            LabelView(outputText: $inputText)
        }
        
        
    }
    
}
LabelView.swift
struct LabelView: View {
    
    @Binding var outputText: String // ←この `@Binding var`
    
    var body: some View {
        
        Text(outputText)
        
    }
    
}

何故このようなコードを書くかと言うと、おそらく多くの人は特に深く考えたことなく、単純に「@State varinputText の値を流し込むから、@Binding var を条件反射で書いた」的な思考ではないかと思います。

しかし、実は outputText に関しては、@Binding var にする必要なく、単純に let でも問題ないのです。

何故 let でいけるのかと言うと、@Binding の役割と、View が何のかを理解しておく必要があります。

まず @Binding はどう言う役割かと言うと、これは「この値を変更するので、変更された値を参照元にも反映させてね」と言うことです。つまりどう言うことかと言うと、もし LabelView において outputText を変更することがあって、さらにその変更を元の MainViewinputText にも反映させたいなら、@Binding を使う必要があると言うことです。そう、平たく言えば @Binding var outputText は、単純に @State var inputTextset メソッドを暴露してるわけです。SwiftUI の哲学に「Single source of Truth(単一の真実の情報源)」があり、@Binding は、その「情報源」である @State への操作の窓口を提供しているだけです。

「でもそしたら、inputText が変わっても outputText の描画が更新されないのでは?」

この問題を理解するのは、次の話:View は何なのか?を理解すればわかるものです。

まず筆者含め、おそらく多くの人が SwiftUIViewclass ではなく struct であることに驚きを感じたはずかと思います。struct は値型であり、「オブジェクト」と言う考え方には当てはまらないですし、当然ながらライフサイクルも存在しません。代入操作がある時はすべての値がコピーされ1var b = a の代入後に b.p を変更してもそれが a.p に反映されることは絶対にあり得ませんし、a の保持がなくなったら例え b の保持がまだ残っていても a のインスタンスが破棄されます。これは今まで参照型の UIView を使い慣れてきた我々 iOS エンジニアには大きなショックを与えたでしょう、何故なら今まで我々が当たり前のようにやってきた「ビューオブジェクトを参照して描画内容を変更」することが理屈上できなくなったのです。

実際も確かにそれができません。それでも SwiftUI がちゃんと描画できるのは、そもそも View はビューオブジェクトそのものではなく、ビューの描画レシピです。つまり「ここをこんな風に描画してね」の指示書に過ぎないのです。

そして、SwiftUI は非常に賢く、「この部分の描画に関わる情報が変わったら、この部分を再描画する」ことがわかっているのです。だから @Binding が全く必要なく、LabelView の描画に inputText の値が使われたから、inputText の値が変わったら LabelView を再描画する必要があることを、SwiftUI がわかっているのです:

MainView.swift
struct MainView: View {
    
    @State var inputText: String
    
    var body: some View {
        
        VStack {
            TextField("Text Input", text: $inputText)
            LabelView(outputText: inputText) // ←Binding していないので `$` も必要ありません
        }
        
        
    }
    
}
LabelView.swift
struct LabelView: View {
    
    let outputText: String // ←ここ `outputText` がそもそも変更することがないので `let` でいけちゃいます
    
    var body: some View {
        
        Text(outputText)
        
    }
    
}

無駄な @Binding をやめて、より簡潔で保守しやすいコードを書いていきましょう :muscle:

  1. 正確には Swift にはパフォーマンス最適化のために Copy-on-Write と言う仕組みがあって、値型でも代入時に値が即コピーされるわけではなく、変更を加えるときに初めて値がコピーされます。ここはその仕組みではなく、あくまでイメージがしやすいために敢えてこの表現を使いました。

21
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?