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

SwiftUIのViewはなぜクロージャでselfを書いても循環参照しないのか

SwiftUIでコードを書いていると、クロージャでViewのselfを頻繁に使うと思います。大抵のiOSアプリ開発者はselfを書かなければいけない場面にのみselfを利用しているので、循環参照しないようにweakもしくはunownedにすることを考慮したくなりますが、SwiftUIのViewはそこは考慮しないでも良いようです。この記事はその理由などを書いています。

なぜSwiftUIでselfが登場しても循環参照しないのか

本題です。SwiftUIでselfを頻繁に使う例として、Buttonのactionクロージャでselfを使う例を利用します。

画面としては次のようなボタンだけのContentViewを考えます。

スクリーンショット 2020-04-19 20.46.24.png

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button(action: { /* [weak self] にそもそもできない */
            self.foo()
        }) {
            Text("ここを押しなさい...")
        }
    }

    func foo() {
        print("foo")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

このようになぜselfをそのまま使っても循環参照しないのかについて箇条書きにします。

  • ContentViewはButtonの参照を保持していない
    • ButtonはViewのComputed Property var body: Self.Body { get } で呼び出されているだけ
  • Buttonはactionクロージャをescapingはしている
  • escapingしているがactionクロージャはContentViewの値をキャプチャしている(値がコピーされている)だけ
    • そもそも[weak self]にできない
    • 構造体なので値がコピーされている
      • おそらくアドレスを調べると別物となっているはず
        • 基本的には構造体のメンバを変更することも出来ない(mutateにできない)

つまり参照をしていないわけです。これを次のように図で概略を示します。

PNGイメージ.png

@ObservedObject@Stateについて

Viewの構造体が保持する@ObservedObject@Stateは変更できる件

こっから話変わります。循環参照の話は終わりです。

「基本的には構造体のメンバを変更することも出来ない」というのを上で書きましたが、Buttonのactionクロージャで@ObservedObject@Stateは変更できるはずです。

import SwiftUI

struct ContentView: View {
    @State var state = false

    var body: some View {
        Button(action: {
            self.state.toggle()
            self.foo()
        }) {
            Text("ここを押しなさい...")
        }
    }

    func foo() {
        print("foo \(state)")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

ボタンを押すと次のようにstateが変化します。

foo true
foo false

Viewの構造体が保持する@ObservedObject@Stateが更新されても値が変わらない件

Viewが更新される際にstructのSubViewは作り直されています。次の記事が参考になるでしょう。

「SwiftUIのSubViewは画面更新ごとに生成と破壊を繰り返す」
https://qiita.com/yimajo/items/a0180e66f4f93120287e

これについては@ObservedObject@Stateは構造体のメンバとなっていますが、これらは「別の領域に保持されている」か、もしくは「Viewの更新時にその値がうまいこと引き継がれる」のでしょう。

ここでリファレンスを読むと、@ObservedObject@StateはプロトコルDynamicPropertyに準拠しており、DynamicPropertyupdateメソッドはbodyの再計算時、つまりViewの更新前に値を再セットするような気がします。

Updates the underlying value of the stored value.

SwiftUIフレームワーク側の定義を読むとそんな感じの事も書いています

/// Represents a stored variable in a `View` type that is dynamically
/// updated from some external property of the view. These variables
/// will be given valid values immediately before `body()` is called.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol DynamicProperty {

    /// Called immediately before the view's body() function is
    /// executed, after updating the values of any dynamic properties
    /// stored in `self`.
    mutating func update()
}

@ObservedObject@Stateなどの変数は、Viewが更新されるbody()呼び出しのタイミングでupdateメソッドが動作し、その際に値が再セットされることでうまいこと引き継がれるんでしょう。

まとめ

  • SwiftUIのViewは構造体
    • (ここに書いてなかったけどクラスにするとコンパイルエラー)
    • クロージャにキャプチャするとコピーされているのでそもそも参照されていない
      • View構造体メンバの@ObservedObject@Stateやらの値はどうやって元のままなの?
        • DynamicPropertyの仕組みによってうまいこと引き継がれている
          • 1描画ごとにView構造体は都度作り直されてもいる
yimajo
株式会社キュリオシティソフトウェアの代表です。iOSアプリを作っています。最近はRxSwift研究読本書いてます。 https://swift.booth.pm/
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
ユーザーは見つかりませんでした