WWDC Quick Look 💓 By SwiftGGTeam
Discover Observation in SwiftUI

Discover Observation in SwiftUI

元の動画を見る

ハイライト

Swift 5.9で導入@Observableマクロ、データモデルの必要性を排除@PublishedそしてObservableObject, SwiftUI は属性の粒度で依存関係を自動的に追跡し、実際に影響を受けるビューのみを更新します。

主な内容

以前のデータ フローの問題点

使用ObservableObject各属性をいつ追加する必要があるか@Published。ビューで使用します@ObservedObjectまたは@StateObjectパッケージ。問題は、モデル内のいずれかが存在する限り、@Publishedプロパティが変更されると、ビューがそのプロパティをまったく使用していない場合でも、ビュー全体が更新されます。

中間ビューがモデルを転送する場合も同様です。親ビューは子ビューに渡され、子ビューは孫ビューに渡されます。属性が変更されるたびに、リンク全体のビューで本文を再計算する必要があります。

@Observable マクロの解決策

01:21@Observableこれは Swift 5.9 のマクロであり、コンパイル時に監視可能な型に自動的に展開されます。開発者は、クラス宣言の前にアノテーションの行を追加するだけで済みます。

@Observable class FoodTruckModel {
    var orders: [Order] = []
    var donuts = Donut.all
}

不要@Published、不要ObservableObjectプロトコル。保存されているすべてのプロパティはデフォルトで監視可能です。

SwiftUI がビュー本体を実行すると、アクセスされたプロパティが自動的に記録されます。後で、これらの記録されたプロパティが変更された場合にのみ、ビューの更新がトリガーされます。

(02:56)orders配列は要素を追加しますが、ビューは読み取りのみですdonutsの場合、このビューは更新されません。属性レベルまで正確に追跡することで、多数の不必要な更新を回避します。

計算されたプロパティもサポートされています

(03:12) 計算されたプロパティが保存されたプロパティで構成されている場合、Observation は以下を自動的に追跡します。

@Observable class FoodTruckModel {
    var orders: [Order] = []
    var donuts = Donut.all
    var orderCount: Int { orders.count }
}

orderCount頼るorders。見る、読むorderCountSwiftUI が基になるものを追跡する時期orders財産。orders変更する場合は必ずお読みくださいorderCountビューが更新されます。

プロパティ ラッパーは 3 つのタイプに簡略化されます

(04:41) SwiftUI で Observable 型を使用する場合、覚えておく必要があるのは 3 つの質問だけです。

  1. このモデルはビュー自体の状態ですか?はい、使用します@State
  2. このモデルはグローバル環境で共有する必要がありますか?はい、使用します@Environment
  3. このモデルにはバインディングのみが必要ですか?はい、使用します@Bindable

3 つの質問すべてに対する答えが「いいえ」の場合は、モデルをビューの通常の属性として使用してください。

@State使用法:

struct DonutListView: View {
    var donutList: DonutList
    @State private var donutToAdd: Donut?

    var body: some View {
        List(donutList.donuts) { DonutView(donut: $0) }
        Button("Add Donut") { donutToAdd = Donut() }
            .sheet(item: $donutToAdd) {
                TextField("Name", text: $donutToAdd.name)
                Button("Save") {
                    donutList.donuts.append(donutToAdd)
                    donutToAdd = nil
                }
                Button("Cancel") { donutToAdd = nil }
            }
    }
}

キーポイント:

  • @State管理プロパティのライフサイクルとビュー バインディング
  • donutToAddこれはビュー自体の状態であり、外部から渡されたモデルではありません。
  • シートに使用$donutToAdd.nameバインディングの作成

@Environment使用法:

@Observable class Account {
    var userName: String?
}

struct FoodTruckMenuView: View {
    @Environment(Account.self) var account

    var body: some View {
        if let name = account.userName {
            HStack { Text(name); Button("Log out") { account.logOut() } }
        } else {
            Button("Login") { account.showLogin() }
        }
    }
}

キーポイント:

  • @Environment(Account.self)指定された型の値を環境から読み取ります
  • タイプ自体を環境キーとして使用し、カスタム キーもサポートします
  • account.userNameにアクセスするため、userName が変更されるとビューが更新されます

@Bindable使用法:

@Observable class Donut {
    var name: String
}

struct DonutView: View {
    @Bindable var donut: Donut

    var body: some View {
        TextField("Name", text: $donut.name)
    }
}

キーポイント:

  • @Bindableこれはバインディングの作成のみを担当し、最も軽量なラッパーです。
  • $donut.name文法の生成Binding<String>
  • TextFieldユーザーが入力したときにバインディング表示値を読み取り、バインディングを書き戻します。

配列内の観測可能なモデル

(07:53) Observable モデルは、配列に配置すると正確に追跡できます。

@Observable class Donut {
    var name: String
}

struct DonutList: View {
    var donuts: [Donut]

    var body: some View {
        List(donuts) { donut in
            HStack {
                Text(donut.name)
                Spacer()
                Button("Randomize") {
                    donut.name = randomName()
                }
            }
        }
    }
}

キーポイント:

  • SwiftUI は配列内の各インスタンスのプロパティ アクセスを追跡します
  • 「ランダム化」をクリックすると、対応する行のみの表示が更新されます。
  • その他のラインdonut.name変更はなく、更新もトリガーされません
  • モデルは入れ子にすることができます: Observable モデルには他の Observable モデルが含まれます

手動観察

(09:18) 計算されたプロパティが外部データ ソースに依存している場合、アクセスと変更を手動でマークする必要があります。

@Observable class Donut {
    var name: String {
        get {
            access(keyPath: \.name)
            return someNonObservableLocation.name
        }
        set {
            withMutation(keyPath: \.name) {
                someNonObservableLocation.name = newValue
            }
        }
    }
}

キーポイント:

  • access(keyPath:)タグ属性が読み取られます
  • withMutation(keyPath:)マーク属性が変更されました
  • ほとんどのシナリオは手動で記述する必要はなく、コンパイラーが自動的にシナリオを合成します。
  • 計算されたプロパティが監視不可能な外部データに依存する場合にのみ必要です

ObservableObject から移行する

(10:25) 移行手順は簡単です。

変更前:

class FoodTruckModel: ObservableObject {
    @Published var orders: [Order] = []
    @Published var donuts = Donut.all
}

変更後:

@Observable class FoodTruckModel {
    var orders: [Order] = []
    var donuts = Donut.all
}

ビューレベルの置換:

  • @ObservedObject→ 通常属性としてそのまま、またはに変更@Bindable(バインディングが必要な場合)
  • @EnvironmentObject@Environment
  • @StateObject@State

変更のほとんどは注釈を削除することです。コードが短くなり、パフォーマンスが向上します。

##詳細

データ フローの意思決定プロセスを完了する

(06:55) どの属性ラッパーを使用するかを決定するデシジョン ツリー:

// 场景 1:视图自己的状态
struct AddDonutSheet: View {
    @State private var newDonut = Donut()

    var body: some View {
        TextField("Name", text: $newDonut.name)
    }
}

// 场景 2:全局共享的数据
@main
struct FoodTruckApp: App {
    let account = Account()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(account)
        }
    }
}

struct ProfileView: View {
    @Environment(Account.self) var account

    var body: some View {
        Text(account.userName ?? "Guest")
    }
}

// 场景 3:只需要绑定
struct DonutEditor: View {
    @Bindable var donut: Donut

    var body: some View {
        Form {
            TextField("Name", text: $donut.name)
            TextField("Flavor", text: $donut.flavor)
        }
    }
}

// 场景 4:不需要包装器
struct DonutRow: View {
    let donut: Donut

    var body: some View {
        Text(donut.name)
    }
}

キーポイント:

  • newDonutビューによって作成および管理されます。@State
  • account複数のビュー間で共有するには、次を使用します。.environment()注射、@Environment読む
  • donut親ビューから渡されます。双方向バインディングが必要です。使用してください。@Bindable
  • donut読み取り専用表示、ラッパーは不要

パフォーマンス上の利点

(04:05) Observation の正確な追跡により、パフォーマンスが大幅に向上します。

@Observable class FoodTruckModel {
    var orders: [Order] = []      // View A 读取
    var donuts = Donut.all        // View B 读取
    var revenue: Decimal = 0      // View C 读取
}
  • orders変更 → ビュー A のみが更新されます
  • donuts変更 → ビュー B のみが更新されます
  • revenue変更 → View C のみが更新されます

使用ObservableObjectの場合、3 つの属性すべてがマークされます@Publishedに変更を加えると、3 つのビューが更新されます。Observable は、更新範囲を最小限に抑えます。

重要ポイント

  1. 既存の SwiftUI プロジェクトのデータ レイヤーをリファクタリングします
  • バンドルObservableObject + @Publishedモデルの移行先@Observable
  • 大量のビューを削除する@ObservedObjectそして@EnvironmentObject
  • エントリ: リーフ ビューから開始して上に移動する、モデルごとの置換
  1. 高パフォーマンスの大規模リスト アプリケーションを構築
  • リスト内の各セルは、Observable モデル インスタンスに対応します。
  • 単一セルのデータを変更すると、そのセルのみが更新されます
  • 入り口:List(items) { ItemRow(item: $0) }ItemRow直接受け取るItem
  1. 再利用可能な編集コンポーネントを設計する
  • 使用@Bindableコンポーネント内で使用されるパッケージング モデルTextFieldToggleバインドされたコントロールを待つ
  • コンポーネントは特定の状態管理方法に依存せず、親ビューによって使用される方法が決定されます。@Stateまだ@Environment
  • 入り口:struct EditorView: View { @Bindable var item: Item }
  1. 複数のビュー間でユーザー ステータスを共有する
  • 使用@Environmentユーザーアカウントや設定などのグローバルステータスをルートビューに挿入します。
  • サブビューはオンデマンドで特定のプロパティを読み取り、プロパティが変更された場合にのみ更新します。
  • 入り口:.environment(userAccount) + @Environment(Account.self)

関連セッション

コメント

GitHub Issues · utterances