İçeriğe geç
academia.sh

Ders 11 / 21

Gözlemlenebilir Nesneler

Gözlemlenebilir bir nesnenin yalnız okunan özelliğinin değişimde yeniden hesaplama ürettiğinin, gerçek gözlem izlemesiyle doğrulanmasının ve nesne düzeyinde bildirimin neyi değiştirdiğinin ölçümü.

Yayın Güncelleme

Bu derste

  • Gözlemlenebilir nesnenin özelliğini okuyan görünümlerin gözlem tetiğiyle yeniden hesaplandığını ölçer
  • Gerçek bir gözlem izlemesiyle yalnız okunan özelliğin değişiminin bildirim ürettiğini gösterir
  • Ekranın kendi durumunun nesne özelliğinden ayrı, yerel kaldığını ayırır
  • Nesne düzeyinde bildirimin özellik düzeyinden farkını modelde kurar
İçindekiler

Önceki derste çocuk görünüm bir değeri bağlamayla okudu; değer yine bir görünümde yaşıyordu. Bu derste değer bir görünümün içinde değil, ayrı bir nesnede yaşar. Ekranın verisi, ağın ya da diskin sonucu, birden çok ekranın paylaştığı bir liste: bunlar bir görünümün alanı olmak yerine bir gözlemlenebilir nesne (observable object) içinde durur. Görünümler nesneyi tutar, özelliklerini okur ve okudukları özellik değişince yeniden hesaplanır. Bu derste bakılan tetik gözlem.

Ölçüm çekirdek dosyayla yapılır ve bu derste ilk kez çekirdeğin dışında, derleyicinin kendi gözlem izlemesi de ölçülür.

// Invalidation.swift — bir degisikligin hangi gorunumu hangi kaynak yoluyla yeniden hesaplattigini siniflandirir
enum Storage {
    case state(owner: String)
    case model(object: String)
    case environment
}

struct ViewNode {
    let name: String
    let parent: String?
    let reads: Set<String>

    init(_ name: String, parent: String?, reads: Set<String>) {
        self.name = name
        self.parent = parent
        self.reads = reads
    }
}

struct Recompute {
    let view: String
    let cause: String
}

struct ViewTree {
    let nodes: [ViewNode]
    let storage: [String: Storage]

    func ancestors(of name: String) -> [String] {
        var found: [String] = []
        var current = nodes.first { $0.name == name }?.parent
        while let next = current {
            found.append(next)
            current = nodes.first { $0.name == next }?.parent
        }
        return found
    }

    func cause(of node: ViewNode, key: String) -> String {
        guard let kind = storage[key] else { return "olcum-disi" }
        switch kind {
        case .environment:
            return "ortam"
        case .model:
            return "gozlem"
        case .state(let owner):
            if owner == node.name { return "yerel" }
            if ancestors(of: node.name).contains(owner) { return "ebeveyn" }
            return "kural-disi"
        }
    }

    func change(_ label: String, key: String) -> [Recompute] {
        let hits = nodes.filter { $0.reads.contains(key) }
            .map { Recompute(view: $0.name, cause: cause(of: $0, key: key)) }
        let skipped = nodes.filter { !$0.reads.contains(key) }.map(\.name)
        let recomputed = hits.isEmpty ? "yok" : hits.map { "\($0.view) \($0.cause)" }.joined(separator: ", ")
        let rest = skipped.isEmpty ? "yok" : skipped.joined(separator: ",")
        print("\(label): \(key) degisti | yeniden \(recomputed) | atlanan \(rest)")
        return hits
    }
}

func countCauses(_ recomputes: [Recompute]) {
    for cause in ["yerel", "ebeveyn", "gozlem", "ortam"] {
        print("\(cause): \(recomputes.filter { $0.cause == cause }.count)")
    }
}

Storage.model(object:) bir değerin bir görünümde değil, bir nesnede saklandığını söyler. cause işlevi bu durumda sahiplik sorusuna girmez: bir nesne özelliğini okuyan her görünüm, ağaçta nerede durursa dursun, gözlem tetiğini alır. Model, çerçevenin bağımlılık izleme kuralını kurar; çerçevenin kendisi değildir.

Özellik Düzeyinde Gözlem

Aşağıdaki nesne bir kütüphanenin kitap listesini ve ödünç sayısını tutar. @Observable makrosu sınıfın her saklanan özelliğini izlenebilir yapar. Ekran nesneyi çocuklarına verir; her çocuk yalnız ilgilendiği özelliği okur.

import SwiftUI
import Observation

@Observable
final class LibraryStore {
    var books: [String] = []
    var borrowedCount = 0
}

struct LibraryScreen: View {
    let store: LibraryStore
    @State private var searchText = ""

    var body: some View {
        VStack {
            BookList(store: store, query: searchText)
            BorrowBadge(store: store)
        }
    }
}

struct BookList: View {
    let store: LibraryStore
    let query: String

    var body: some View {
        List(store.books.filter { $0.contains(query) }, id: \.self) { title in
            Text(title)
        }
    }
}

struct BorrowBadge: View {
    let store: LibraryStore

    var body: some View {
        Text("Odunc: \(store.borrowedCount)")
    }
}

BookList yalnız books özelliğini, BorrowBadge yalnız borrowedCount özelliğini okur. searchText ise ekranın kendi durumudur ve nesnede değildir. Ağaç bu okumaları birebir taşır. Saklama tablosunda iki nesne özelliği LibraryStore nesnesine, arama metni ise LibraryScreen görünümüne bağlanır. Üç değişiklik verilir: bir kitap eklenir, bir ödünç sayılır, bir arama yazılır.

// main.swift — nesne ozelligi gozlem, ekranin kendi durumu yerel tetik uretir
let storage: [String: Storage] = [
    "books": .model(object: "LibraryStore"),
    "borrowedCount": .model(object: "LibraryStore"),
    "searchText": .state(owner: "LibraryScreen"),
]

let screen = ViewTree(nodes: [
    ViewNode("LibraryScreen", parent: nil, reads: ["searchText"]),
    ViewNode("BookList", parent: "LibraryScreen", reads: ["books", "searchText"]),
    ViewNode("BorrowBadge", parent: "LibraryScreen", reads: ["borrowedCount"]),
], storage: storage)

let recomputes = [
    screen.change("kitap-eklendi", key: "books"),
    screen.change("odunc-verildi", key: "borrowedCount"),
    screen.change("arama-yazildi", key: "searchText"),
].flatMap { $0 }

countCauses(recomputes)
kitap-eklendi: books degisti | yeniden BookList gozlem | atlanan LibraryScreen,BorrowBadge
odunc-verildi: borrowedCount degisti | yeniden BorrowBadge gozlem | atlanan LibraryScreen,BookList
arama-yazildi: searchText degisti | yeniden LibraryScreen yerel, BookList ebeveyn | atlanan BorrowBadge
yerel: 1
ebeveyn: 1
gozlem: 2
ortam: 0

kitap-eklendi yalnız BookList’i, odunc-verildi yalnız BorrowBadge’i yeniden hesaplattı; ikisi de gözlem. İki görünüm aynı nesneyi tutuyor, ama her biri nesnenin farklı bir özelliğini okuyor ve birinin değişimi ötekini etkilemiyor. arama-yazildi satırı gözlem değil: değer nesnede değil ekranda yaşıyor, bu yüzden LibraryScreen yerel, BookList ebeveyn tetikle yeniden hesaplandı. Sayılar: yerel bir, ebeveyn bir, gözlem iki.

Burada tetiği belirleyen nesnenin kendisi değil, okunan özelliktir. Aynı BookList hem books için gözlem hem searchText için ebeveyn tetikle yeniden hesaplandı. Bu, ikinci dersin sonucunun nesneler için de geçerli olduğunu gösterir: tetik görünümün türüne değil, okunan değerin yaşadığı yere bağlıdır.

DR7. Bu ölçüm üç değişiklik ve üç anahtar için. Nesne özelliğinin görünümün gövdesinde yalnız dolaylı okunduğu durum, örneğin bir hesaplanmış özellik üzerinden okuma, ölçülmedi; modelde her görünüm okuduğu anahtarı doğrudan reads içinde taşır.

Gerçek İzleme: Yalnız Okunan Özellik

Yukarıdaki ölçüm bir modeldir; ağaç kuralı bildiğimiz biçimde kurar. Kuralın kendisi, yani @Observable bir nesnede yalnız okunan özelliğin değişiminin bildirim üretmesi, derleyicinin ve Observation modülünün davranışıdır ve doğrudan ölçülebilir. withObservationTracking, ilk kapanışta okunan özellikleri izler ve bunlardan biri değişince ikinci kapanışı çağırır.

// main.swift — gercek gozlem izleme: yalniz okunan ozellik bildirim uretir
import Observation

@Observable
final class LibraryStore {
    var books: [String] = []
    var borrowedCount = 0
}

let library = LibraryStore()
withObservationTracking {
    _ = library.borrowedCount
} onChange: {
    print("izlenen ozellik degisti")
}
library.books.append("algoritmalar")
print("books degisti")
library.borrowedCount += 1
print("borrowedCount degisti 1")
library.borrowedCount += 1
print("borrowedCount degisti 2")
books degisti
izlenen ozellik degisti
borrowedCount degisti 1
borrowedCount degisti 2

İzleme yalnız borrowedCount özelliğini okudu. books değiştiğinde geri çağrı çalışmadı: çıktıda books degisti satırından önce bir bildirim yok. borrowedCount değiştiğinde geri çağrı çalıştı ve izlenen ozellik degisti satırı, atamanın hemen ardından, borrowedCount degisti 1 satırından önce basıldı. Yani bildirim atamanın içinde ve eşzamanlı geldi. İkinci artışta ise bildirim gelmedi: izleme bir kezliktir. Bir değişiklik bildirildikten sonra izleme biter ve sonraki değişikliği görmek için yeniden kurulması gerekir. Görünümlerde bu yeniden kurma çerçevenin işidir; bu ders onu ölçmez.

Bu iki gözlem, ağaçtaki iki satırı destekler. Okunmayan özelliğin değişimi hiçbir şeyi yeniden hesaplatmaz, okunan özelliğin değişimi yalnız o okumayı kuran tarafı bilgilendirir. Ağaçta BorrowBadge’in books değişiminde atlanması bu davranışın modele yansımasıdır.

DR8. Bu ölçüm Observation modülünün kendisi içindir; çerçevenin bu izlemeyi görünüm gövdesine nasıl bağladığı ve gövdenin kaç kez çağrıldığı ölçülmedi. Geri çağrının atanan değer yazılmadan önce mi sonra mı çalıştığı da ölçülmedi; çıktı yalnız atamanın içinde çalıştığını gösterir.

Sınırlayıcı Ölçüm

Her gözlem mekanizması özellik düzeyinde değildir. Daha eski bir yol, ObservableObject ve @Published, nesne düzeyinde bildirim yapar: nesnenin herhangi bir yayımlanan özelliği değişince nesneyi izleyen her görünüm yeniden hesaplanır. Bu yol Combine çerçevesine bağlıdır; ölçüm ortamında derlenmediği için kaynağı yalnız gösterilebilir.

import SwiftUI
import Combine

final class LibraryStore: ObservableObject {
    @Published var books: [String] = []
    @Published var borrowedCount = 0
}

struct BookList: View {
    @ObservedObject var store: LibraryStore

    var body: some View {
        List(store.books, id: \.self) { title in
            Text(title)
        }
    }
}

struct BorrowBadge: View {
    @ObservedObject var store: LibraryStore

    var body: some View {
        Text("Odunc: \(store.borrowedCount)")
    }
}

Aşağıdaki ölçüm bu kuralın modelini kurar; çerçevenin kendisini ölçmez. Nesne düzeyindeki bildirimde saklama tablosunda tek bir anahtar vardır ve anahtar nesnenin adıdır. İzleyen her görünüm bu anahtarı okur, çünkü nesnedeki hangi özelliğin değiştiği görünüm tarafından ayırt edilmez.

// main.swift — nesne duzeyinde bildirimde anahtar nesnenin adidir
let storage: [String: Storage] = [
    "LibraryStore": .model(object: "LibraryStore"),
]

let screen = ViewTree(nodes: [
    ViewNode("LibraryScreen", parent: nil, reads: []),
    ViewNode("BookList", parent: "LibraryScreen", reads: ["LibraryStore"]),
    ViewNode("BorrowBadge", parent: "LibraryScreen", reads: ["LibraryStore"]),
], storage: storage)

let recomputes = [
    screen.change("kitap-eklendi", key: "LibraryStore"),
    screen.change("odunc-verildi", key: "LibraryStore"),
].flatMap { $0 }

countCauses(recomputes)
kitap-eklendi: LibraryStore degisti | yeniden BookList gozlem, BorrowBadge gozlem | atlanan LibraryScreen
odunc-verildi: LibraryStore degisti | yeniden BookList gozlem, BorrowBadge gozlem | atlanan LibraryScreen
yerel: 0
ebeveyn: 0
gozlem: 4
ortam: 0

İki değişiklik iki görünümü ikişer kez yeniden hesaplattı; gözlem sayısı dört. Özellik düzeyindeki ilk ölçümde aynı iki değişiklik iki yeniden hesaplama üretmişti. Fark, kuralın ayrıntısından gelir: nesne düzeyinde bildirimde anahtar nesnenin adı olduğu için, kitap-eklendi değişikliği BorrowBadge’i de yeniden hesaplattı. Bu satırlar çerçevenin ölçümü değildir, kuralın modelidir. Ders bu modeli, özellik düzeyindeki gözlemin neyi kazandırdığını görmek için kurar: değişen özelliği okumayan görünüm atlanır.

DR9. Bu ölçüm nesne düzeyinde bildirimin kuralını model olarak kurar. Gerçek ObservableObject nesnesinin yayımlama biçimi ve görünümün aynı değerle gelen yayında yeniden hesaplanıp hesaplanmadığı ölçülmedi.

Özet

  • Gözlemlenebilir nesnenin özelliğini okuyan görünüm, özellik değişince gözlem tetiğiyle yeniden hesaplanır.
  • Gözlem özellik düzeyindedir: yalnız okunan özelliğin değişimi bildirim üretir, okunmayan özellik üretmez.
  • Gerçek izleme bir kezliktir; ikinci değişikliği görmek için izleme yeniden kurulur.
  • Ekranın kendi durumu nesne özelliğinden ayrıdır ve yerel ya da ebeveyn tetik üretir.
  • Nesne düzeyinde bildirim, nesneyi izleyen her görünümü her değişiklikte yeniden hesaplatır; özellik düzeyinde gözlem bunu ayırır.
  • Bu dersin tetiği: gözlem.

Sonraki Adım

Bu derste nesneyi çocuklara parametre olarak verdik; ekran nesneyi tuttu, çocuk onu aldı. Nesneyi ağacın her görünümüne tek tek taşımak, aradaki görünümleri yalnızca iletmek için nesneyi tanımaya zorlar. Sıradaki derste değer ağaç boyunca bir kez konur ve altındaki her görünüm onu doğrudan okur. Tetik bu kez ortamdır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat