🌊

WWDC23で追加されたSwiftUIのObservationについて

2023/08/23に公開

Observableについて

Observationは、プロパティの変更を追跡するためのSwiftの新機能です。

使い方としては@Observableを追加するだけで、データモデルの変更にUIが反応するようになります。

(Xcode 15 beta)
import Observation
@Observable class FoodTruckModel {
   var orders: [Order] = []
   var donuts = Donut.all
}

このように@Observableマクロを使用して、特定のプロパティの変更を監視します。

UI更新ロジック、ObservationとCombineの違い

ObservationのUI更新ロジックは、特定のプロパティが変更されたときにのみUIを更新します。これにより、不必要なUIのリフレッシュが減少し、パフォーマンスが向上します。

struct DonutMenu: View {
    let model: FoodTruckModel

    var body: some View {
        List {
            Section("Donuts") {
                ForEach(model.donuts) { donut in
                    Text(donut.name)
                }
            }
            Button("Add new donut") {
                model.addDonut()
            }
        }
    }
}

上記のコードでは、DonutMenuビューは、FoodTruckModelのインスタンスをプロパティとして持ち、ビューのbodyが実行されるとき、SwiftUIはこのビューがmodel.donutsプロパティに依存していることを認識します。そして、model.donutsプロパティが変更されたときには、このビューを再描画する必要があると判断します。
"Add new donut"ボタンをクリックすると、model.addDonut()メソッドが呼び出され、新しいドーナツを追加しプロパティが変更されDonutMenuビューを更新します。

それに比べて、CombineのUI更新ロジックは、データの変更が発生すると、その変更を購読しているすべてのオブザーバーに通知します。その結果、UIは新しいデータに基づいて更新されます。ただし、Combineはプロパティレベルでの正確な観察を提供できず、不必要なUIのリフレッシュを引き起こす可能性があります。

class User: ObservableObject {
    @Published var firstName: String
    @Published var lastName: String
    @Published var age: Int

    init(firstName: String, lastName: String, age: Int) {
        self.firstName = firstName
        self.lastName = lastName
        self.age = age
    }
}
struct UserProfileView: View {
    @ObservedObject var user: User

    var body: some View {
        VStack {
            Text(user.firstName)
            Text(user.lastName)
        }
    }
}

このビューはfirstNameとlastNameプロパティを表示しますが、ageプロパティは表示しません。しかし、Userクラスのageプロパティが変更されると、@Publishedプロパティラッパーにより通知が発行され、UserProfileViewがageプロパティを表示していないにも関わらず更新されてしまいます。

このように、Combineを使用すると、関連しないプロパティの変更によってもUIが更新される可能性があります。これが不必要なUIのリフレッシュを引き起こす原因となります。

追加されたプロパティラッパーや関数

1. @ObservationIgnored

特定のプロパティの観察を無視します。これは、特定のプロパティが変更されても通知を送信したくない場合に使用します。

@Observable
class Store {
    var firstName: String = ""
    var lastName: String = ""
    var fullName: String {
        firstName + " " + lastName
    }
    @ObservationIgnored private var count: Int = 0
    init(firstName: String, lastName: String, count: Int) {
        self.firstName = firstName
        self.lastName = lastName
        self.count = count
    }
}
2. withObservationTracking

withObservationTracking内でtextを監視し、textが空でない場合にのみshowSendButtonをtrueに変更

@Observable final class ChatViewModel {
    var text: String = ""
    var showSendButton = false
    
    init() {
        setLocalPublishers()
    }
    
    
    func setLocalPublishers() {
        withObservationTracking {
            _ = text
        } onChange: { [weak self] in
            guard let self else { return }
            showSendButton = !text.isEmpty
        }
    }
}

Observationの利点

Observationの主な利点は以下の通りです。

シンプルなモデル: Observationを使用すると、モデルをこれまで以上にシンプルにすることができます。プロパティの変更を追跡するための新機能であるObservationは、プロパティラッパーを必要とせずにSwift UIビューを動かすことができます。

パフォーマンスの向上: Observationは、特定のプロパティが変更されたときだけビューのボディを再計算することができます。これにより、不必要なUIの更新を避け、パフォーマンスを向上させることができます。

直感的なバインディング: Observationは、ドル記号の構文を使用してプロパティをバインドすることができます。これにより、開発者は直感的にバインディングを作成し、UIの更新を制御することができます。

コードの簡略化: Observableマクロを使用すると、コードを簡略化し、パフォーマンスを向上させる可能性があります。これにより、開発者はよりシンプルでパフォーマンスの高いコードを書くことができます。

以上のように、ObservationはSwift UIでの開発をシンプルで直感的にし、パフォーマンスを向上させる機能を提供します。

Arsaga Developers Blog

Discussion