İçeriğe geç
academia.sh

Ders 06 / 21

Listeler ve Formlar

Modelin koleksiyonunu okuyan listenin gözlem tetiğiyle, form alanının ebeveyn durumuna bağlanmasının ebeveyn tetiğiyle yeniden hesaplandığının ve tek satırın özelliği değişince öteki satırların atlandığının ölçümü.

Yayın Güncelleme

Bu derste

  • Listenin satırlarını bir koleksiyondan kimlikle nasıl kurduğunu kaynak parçada okur
  • Modelin koleksiyonu değişince listenin gözlem tetiğiyle yeniden hesaplandığını ölçer
  • Form alanının ebeveyn durumuna bağlandığında ebeveyn tetiği ürettiğini gösterir
  • Tek bir satırın özelliği değişince öteki satırların yeniden hesaplanmadığını ölçer
İçindekiler

Önceki derste ızgaranın verisi ebeveynden, sütun sayısı ortamdan geldi. Bu derste bir listenin satırlarının kaynağına bakıyoruz: koleksiyon çoğu zaman ağacın dışında yaşayan bir modeldedir. Liste bu koleksiyonu okur ve her öğe için bir satır kurar. Form ise aynı listeye benzer ama içindeki alanlar, değerlerini bir üst görünümün durumuna bağlama ile bağlar. Soru aynı kalır: bu yeniden hesaplamayı ne tetikledi? Bu derste bakılan tetik, gözlem.

// 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)")
    }
}

Çekirdek, açılış dersindekiyle aynıdır. Bu dersin ağacı bir not kutusudur: ekran, form, liste ve iki satır.

Koleksiyondan Satıra

Liste, öğeleri kimlikle ayırt edilen bir koleksiyondan satırlarını kurar. Kimlik, bir öğenin koleksiyonda yeri değişse bile aynı satıra karşılık gelmesini sağlar. Aşağıdaki parça modelin koleksiyonunu bir listeye bağlar.

// Kaynak parca: modelin koleksiyonundan liste
import SwiftUI
import Observation

@Observable
final class NoteStore {
    var notes: [Note] = []
}

@Observable
final class Note: Identifiable {
    let id: Int
    var title: String

    init(id: Int, title: String) {
        self.id = id
        self.title = title
    }
}

struct InboxList: View {
    let store: NoteStore

    var body: some View {
        List(store.notes) { note in
            Text(note.title)
        }
    }
}

store.notes listenin gövdesinde okunur; bu, gözlemlenen bir modelin değerini okumaktır. Koleksiyon değiştiğinde, yani bir not eklendiğinde ya da silindiğinde, listenin gövdesi yeniden hesaplanır. Note da @Observable bir sınıftır: her not kendi başına gözlemlenen bir nesnedir ve satır note.title değerini okuduğunda o notun başlığına bağlanır. Model, çerçevenin bağımlılık izleme kuralını kurar; çerçevenin kendisi değildir.

GV17. Bu ölçüm koleksiyonu tek bir notes anahtarı olarak modeller. Koleksiyonun bir öğesinin sırasının değişmesi ile öğe eklenmesinin farklı sonuç verip vermediği ölçülmedi.

Liste, Form ve Satır Durumu

Aşağıdaki ağaçta ekran, taslak başlığın sahibidir; form alanı bu değere bağlama ile bağlıdır. Liste modelin koleksiyonunu okur. İlk satırın başlığı o satırın Note nesnesinden gelir, açık ya da kapalı olması ise satırın kendi durumudur.

// main.swift — koleksiyon gozlem, form alani ebeveyn, satir durumu yerel
let storage: [String: Storage] = [
    "notes": .model(object: "NoteStore"),
    "draftTitle": .state(owner: "InboxScreen"),
    "noteTitle1": .model(object: "Note1"),
    "noteTitle2": .model(object: "Note2"),
    "expanded1": .state(owner: "NoteRow1"),
    "expanded2": .state(owner: "NoteRow2"),
]

let screen = ViewTree(nodes: [
    ViewNode("InboxScreen", parent: nil, reads: ["draftTitle"]),
    ViewNode("NoteForm", parent: "InboxScreen", reads: ["draftTitle"]),
    ViewNode("InboxList", parent: "InboxScreen", reads: ["notes"]),
    ViewNode("NoteRow1", parent: "InboxList", reads: ["noteTitle1", "expanded1"]),
    ViewNode("NoteRow2", parent: "InboxList", reads: ["noteTitle2", "expanded2"]),
], storage: storage)

let recomputes = [
    screen.change("koleksiyon-degisti", key: "notes"),
    screen.change("taslak-yazildi", key: "draftTitle"),
    screen.change("satir-acildi", key: "expanded1"),
].flatMap { $0 }

countCauses(recomputes)
koleksiyon-degisti: notes degisti | yeniden InboxList gozlem | atlanan InboxScreen,NoteForm,NoteRow1,NoteRow2
taslak-yazildi: draftTitle degisti | yeniden InboxScreen yerel, NoteForm ebeveyn | atlanan InboxList,NoteRow1,NoteRow2
satir-acildi: expanded1 degisti | yeniden NoteRow1 yerel | atlanan InboxScreen,NoteForm,InboxList,NoteRow2
yerel: 2
ebeveyn: 1
gozlem: 1
ortam: 0

koleksiyon-degisti satırında yalnız liste gözlem tetiğiyle yeniden hesaplandı; ekran, form ve satırlar atlandı. Satırlar koleksiyonu değil, kendi başlıklarını okuyor. taslak-yazildi satırında ekran yerel, form ebeveyn tetiğiyle yeniden hesaplandı: form alanı değerini ekranın durumuna bağlama ile bağlar ve ekranın durumu değişince alan da yeniden hesaplanır. satir-acildi satırında yalnız ilk satır yerel tetikle yeniden hesaplandı.

Üç değişiklik üç ayrı tetik üretti: gözlem, ebeveyn ve yerel. Modelin koleksiyonu değişince tek bir görünüm yeniden hesaplandı; bu, listenin koleksiyonu okuyan tek görünüm olmasının sonucudur.

GV18. Bu ölçüm notes, draftTitle ve expanded1 için. Bağlama ile bağlı alanın değerini değiştirdiğinde ekranın mı yoksa alanın mı önce yeniden hesaplandığı ölçülmedi.

Form Alanı ve Bağlama

Form alanı, değerini okuyup değiştirebilmek için bir bağlamaya ihtiyaç duyar. Bağlama, değerin sahibine bir yol verir; alan yeni değeri bu yoldan yazar. Aşağıdaki parça bir metin alanını ekranın durumuna bağlar.

// Kaynak parca: form alani ebeveynin durumuna baglama ile baglanir
import SwiftUI

struct InboxScreen: View {
    @State private var draftTitle = ""

    var body: some View {
        NoteForm(title: $draftTitle)
    }
}

struct NoteForm: View {
    @Binding var title: String

    var body: some View {
        Form {
            TextField("Baslik", text: $title)
        }
    }
}

$draftTitle ifadesi, durumun kendisini değil ona giden bir yolu verir; form bu yolu title bağlaması olarak alır ve metin alanına iletir. Alan yazıldıkça durumun sahibi güncellenir ve sahibin gövdesi yeniden hesaplanır. Modelde bu, draftTitle anahtarının ekran tarafından sahiplenilip form tarafından okunmasıdır.

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

Bir listede bir satırın özelliği değişince öteki satırlar bu değeri okumaz. Aşağıdaki ağaçta liste koleksiyonu, her satır ise yalnız kendi Note nesnesinin başlığını okur.

// main.swift — tek satirin basligi degisince oteki satirlar atlanir
let storage: [String: Storage] = [
    "notes": .model(object: "NoteStore"),
    "noteTitle1": .model(object: "Note1"),
    "noteTitle2": .model(object: "Note2"),
    "noteTitle3": .model(object: "Note3"),
]

let screen = ViewTree(nodes: [
    ViewNode("InboxList", parent: nil, reads: ["notes"]),
    ViewNode("NoteRow1", parent: "InboxList", reads: ["noteTitle1"]),
    ViewNode("NoteRow2", parent: "InboxList", reads: ["noteTitle2"]),
    ViewNode("NoteRow3", parent: "InboxList", reads: ["noteTitle3"]),
], storage: storage)

let recomputes = [
    screen.change("baslik-degisti", key: "noteTitle2"),
    screen.change("not-eklendi", key: "notes"),
].flatMap { $0 }

countCauses(recomputes)
baslik-degisti: noteTitle2 degisti | yeniden NoteRow2 gozlem | atlanan InboxList,NoteRow1,NoteRow3
not-eklendi: notes degisti | yeniden InboxList gozlem | atlanan NoteRow1,NoteRow2,NoteRow3
yerel: 0
ebeveyn: 0
gozlem: 2
ortam: 0

baslik-degisti satırında yalnız ikinci satır gözlem tetiğiyle yeniden hesaplandı; liste ve öteki iki satır atlandı. not-eklendi satırında ise yalnız liste yeniden hesaplandı: satırların başlıkları değişmediği için hiçbir satır yeniden hesaplanmadı. Bu, bir satırın başlığının değişmesinin listenin yeniden hesaplanmasını gerektirmediğini gösterir.

Sayaç iki gözlem tetiği gösterir ve başka tetik yoktur. Başlığı ayrı bir anahtar yapan, Note’un sınıf olmasıdır: bir notun başlığını yazmak notes dizisini değiştirmez, dizi aynı nesnelere giden aynı başvuruları tutmaya devam eder. Büyük bir listede bunun anlamı şudur: bir satırı güncelleyen yazma yalnız o satırı okuyan görünümü etkiler.

GV19. Bu ölçüm üç satırlı bir listeyi ve her satırın tek bir başlık anahtarı okumasını modeller. Çerçevenin satır kimliğini koruma biçimi, kaydırma sırasında görünmeyen satırların davranışı ve büyük koleksiyonlarda ölçek ölçülmedi. Note bir struct olsaydı başlık yazması notes dizisine yazma sayılır ve ağaçta notes anahtarı değişirdi; bu modelleme burada kurulmadı.

Özet

  • Liste, öğeleri kimlikle ayırt edilen bir koleksiyondan satırlarını kurar.
  • Modelin koleksiyonu değişince, koleksiyonu okuyan liste gözlem tetiğiyle yeniden hesaplanır.
  • Form alanı değerini bağlama ile ebeveynin durumuna bağlar; durum değişince alan ebeveyn tetiğiyle yeniden hesaplanır.
  • Satırın kendi açık ya da kapalı durumu yerel tetik üretir.
  • Bir satırın başlığı değişince yalnız o satırı okuyan görünüm yeniden hesaplanır; öteki satırlar atlanır.
  • Bu dersin tetiği: gözlem.

Sonraki Adım

Bu derste tetik, ağacın dışındaki bir modelden geldi. Sıradaki derste görsel bileşenlere bakıyoruz: bir simgenin ya da şeklin rengi hangi değere bağlıdır. Cevap yine ortamdan gelen bir değerdir, bu kez renk şemasıdı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