Ders 12 / 21
Ortam Değerleri
Ortam değerinin ağaç boyunca ara görünümlere dokunmadan yalnız okuyanı yeniden hesaplattığının, ortama konan nesnenin özelliğinin ortam değil gözlem tetiklediğinin ve alt ağaçta ezmenin modelde nasıl kurulduğunun ölçümü.
Yayın Güncelleme
Bu derste
- Ortam değerinin ağacın derinindeki okuyanı, aradaki görünümleri atlayarak yeniden hesaplattığını ölçer
- Sistem ortam değeri ile özel ortam anahtarının aynı tetiği ürettiğini gösterir
- Ortama konan nesnenin özelliğinin değişiminin ortam değil gözlem sayıldığını ayırır
- Alt ağaçta ezmenin tek anahtarlı modelde ayrı bir anahtar gerektirdiğini görür
İçindekiler
Önceki derste bir nesne ayrı yaşıyordu ve ona ulaşmak için nesneyi her çocuğa parametre olarak verdik. Aradaki görünümler nesneyle ilgilenmese bile onu taşımak zorundaydı. Ortam bu taşımayı ortadan kaldırır: değer ağacın bir noktasında konur ve altındaki her görünüm onu doğrudan okuyabilir. Bu derste bakılan tetik ortam.
Ölçüm yine çekirdek dosyayla yapı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.environment bir değerin hiçbir görünüme ait olmadığını, ağacın o noktasından aşağı akan bir yayın olduğunu söyler. cause işlevi bu anahtarı okuyan her görünüme sahiplik sorusu sormadan ortam tetiğini verir. Model, çerçevenin bağımlılık izleme kuralını kurar; çerçevenin kendisi değildir.
Derindeki Okuyan
Çerçeve bazı ortam değerlerini kendisi sağlar: renk kipi, yazı boyutu ayarı, yerel ayar. Uygulama kendi anahtarlarını da ekleyebilir. Özel bir anahtar EnvironmentValues üzerine bir uzantıyla tanımlanır ve @Entry makrosu varsayılan değeriyle birlikte anahtarı kurar. Bir görünüm değeri @Environment ile okur; değer bir üst görünümde .environment değiştiricisiyle konur.
import SwiftUI
extension EnvironmentValues {
@Entry var textScale: Int = 1
}
struct ReaderScreen: View {
var body: some View {
HStack {
ArticleView()
SidebarView()
}
.environment(\.textScale, 2)
}
}
struct ArticleView: View {
var body: some View {
VStack {
ParagraphView()
CaptionView()
}
}
}
struct ParagraphView: View {
@Environment(\.textScale) private var textScale
var body: some View {
Text("Olcek \(textScale)")
}
}
struct CaptionView: View {
@Environment(\.textScale) private var textScale
var body: some View {
Text("Alt yazi \(textScale)")
}
}
struct SidebarView: View {
@Environment(\.colorScheme) private var colorScheme
var body: some View {
Text(colorScheme == .dark ? "Koyu" : "Acik")
}
}
ArticleView hiçbir ortam değerini okumaz; yalnız iki çocuğu barındırır. ParagraphView ve CaptionView özel textScale anahtarını, SidebarView ise sistemin colorScheme değerini okur. Ağaçta bu üç okuma reads içindedir. İki değişiklik verilir: biri özel anahtarın değeri, biri sistemin renk kipi. İlki uygulamanın .environment değiştiricisinin değerini değiştirmesiyle, ikincisi kullanıcının sistem ayarını değiştirmesiyle olur; ikisi de ortam değeridir.
// main.swift — ortam degeri araya dokunmadan yalniz okuyani yeniden hesaplatir
let storage: [String: Storage] = [
"textScale": .environment,
"colorScheme": .environment,
]
let screen = ViewTree(nodes: [
ViewNode("ReaderScreen", parent: nil, reads: []),
ViewNode("ArticleView", parent: "ReaderScreen", reads: []),
ViewNode("ParagraphView", parent: "ArticleView", reads: ["textScale"]),
ViewNode("CaptionView", parent: "ArticleView", reads: ["textScale"]),
ViewNode("SidebarView", parent: "ReaderScreen", reads: ["colorScheme"]),
], storage: storage)
let recomputes = [
screen.change("olcek-degisti", key: "textScale"),
screen.change("kip-degisti", key: "colorScheme"),
].flatMap { $0 }
countCauses(recomputes)
olcek-degisti: textScale degisti | yeniden ParagraphView ortam, CaptionView ortam | atlanan ReaderScreen,ArticleView,SidebarView kip-degisti: colorScheme degisti | yeniden SidebarView ortam | atlanan ReaderScreen,ArticleView,ParagraphView,CaptionView yerel: 0 ebeveyn: 0 gozlem: 0 ortam: 3
olcek-degisti satırında iki görünüm, ParagraphView ve CaptionView, yeniden hesaplandı; ikisi de torundur; ata ReaderScreen ise değeri koyan görünümdür. ArticleView aradaydı ve atlandı: ortam değerini okumadığı için değişiklik ona dokunmadı. kip-degisti satırında yalnız SidebarView yeniden hesaplandı. Üç yeniden hesaplamanın üçü de ortam; sayımda ortam üç, geri kalanlar sıfır.
Ortamın getirisi bu atlanan satırdadır. Değer ağacın iki seviye altına parametreyle taşınsaydı, ArticleView bunu parametre olarak alıp yeniden iletmek zorunda kalırdı ve değer değişince kendisi de okuyan sayılırdı. Ortamda aradaki görünüm değeri bilmez, dolayısıyla değişimden etkilenmez.
DR10. Bu ölçüm iki ortam anahtarı ve iki değişiklik için. Sistemin renk kipi gerçekte kullanıcının ayarıyla değişir; ölçümde ayar bir anahtar değişikliği olarak temsil edildi ve ayarın değişimini çerçevenin nasıl ilettiği ölçülmedi.
Ortama Konan Nesne
Ortama yalnız basit değerler konmaz; bir gözlemlenebilir nesne de konabilir. Nesne .environment(session) ile verilir ve @Environment(SessionStore.self) ile okunur. Bu durumda iki ayrı şey değişebilir: nesnenin kendisi ya da nesnenin bir özelliği.
import SwiftUI
import Observation
@Observable
final class SessionStore {
var unreadCount = 0
}
struct InboxBadge: View {
@Environment(SessionStore.self) private var session
var body: some View {
Text("Okunmamis: \(session.unreadCount)")
}
}
InboxBadge nesneyi ortamdan alır, ama gövdesinde okuduğu şey nesnenin unreadCount özelliğidir. Dolayısıyla izlenen şey, ortamın kendisi değil, bir nesne özelliğidir. Ağaçta bu iki ayrım iki farklı saklama olarak yazılır: textScale ortam, unreadCount bir nesne özelliği.
// main.swift — ortama konan nesnenin ozelligi ortam degil gozlem tetikler
let storage: [String: Storage] = [
"textScale": .environment,
"unreadCount": .model(object: "SessionStore"),
]
let screen = ViewTree(nodes: [
ViewNode("ReaderScreen", parent: nil, reads: []),
ViewNode("ParagraphView", parent: "ReaderScreen", reads: ["textScale"]),
ViewNode("InboxBadge", parent: "ReaderScreen", reads: ["unreadCount"]),
], storage: storage)
let recomputes = [
screen.change("olcek-degisti", key: "textScale"),
screen.change("okunmamis-degisti", key: "unreadCount"),
].flatMap { $0 }
countCauses(recomputes)
olcek-degisti: textScale degisti | yeniden ParagraphView ortam | atlanan ReaderScreen,InboxBadge okunmamis-degisti: unreadCount degisti | yeniden InboxBadge gozlem | atlanan ReaderScreen,ParagraphView yerel: 0 ebeveyn: 0 gozlem: 1 ortam: 1
İki değişiklik iki yeniden hesaplama üretti ve tetikler farklı: olcek-degisti ortam, okunmamis-degisti gözlem. InboxBadge ortam yoluyla ulaştığı nesneyi okuyor, ama onu yeniden hesaplatan şey ortam değil, nesne özelliğidir. Ortamdan alınan nesne yalnızca erişim yoludur; tetiği değerin saklandığı yer belirler. Bu, üçüncü dersin kuralıyla örtüşür: nesne özelliğini kim ve nasıl okursa okusun tetik gözlemdir.
Sayılar: gözlem bir, ortam bir.
DR11. Bu ölçüm yalnız nesnenin özelliğinin değişimi için. Ortamdaki nesnenin tamamen başka bir nesneyle değiştirildiği durum, yani ortam değerinin kendisinin değişimi, ölçülmedi; o durumda ortam tetiği beklenir ama model bunu bir anahtar değişikliği olarak ayrıca temsil etmedi.
Sınırlayıcı Ölçüm
Ortam değeri ağacın bir noktasında ezilebilir: alt ağaçta aynı anahtara başka bir değer konur ve o alt ağaçtaki görünümler üst değeri değil ezilen değeri okur. Üstteki değerin değişimi bu alt ağacı ilgilendirmez. Model ise her anahtar için tek bir saklama satırı tutar; aynı anahtarın iki farklı değeri olamaz.
import SwiftUI
struct ReaderScreen: View {
var body: some View {
HStack {
ArticlePane()
SidePane()
.environment(\.textScale, 3)
}
.environment(\.textScale, 2)
}
}
Aşağıdaki ölçümde ezme bu yüzden iki ayrı anahtarla temsil edilir: textScale üst değeri, paneTextScale yan bölmedeki ezilen değeri taşır. Hangi görünümün hangisini okuduğunu kaynağa bakarak biz yazıyoruz; model bunu kendisi bulamaz.
// main.swift — alt agacta ezme modelde ayri bir anahtardir
let storage: [String: Storage] = [
"textScale": .environment,
"paneTextScale": .environment,
]
let screen = ViewTree(nodes: [
ViewNode("ReaderScreen", parent: nil, reads: []),
ViewNode("ArticlePane", parent: "ReaderScreen", reads: ["textScale"]),
ViewNode("SidePane", parent: "ReaderScreen", reads: ["paneTextScale"]),
], storage: storage)
let recomputes = [
screen.change("ust-olcek-degisti", key: "textScale"),
screen.change("yan-olcek-degisti", key: "paneTextScale"),
].flatMap { $0 }
countCauses(recomputes)
ust-olcek-degisti: textScale degisti | yeniden ArticlePane ortam | atlanan ReaderScreen,SidePane yan-olcek-degisti: paneTextScale degisti | yeniden SidePane ortam | atlanan ReaderScreen,ArticlePane yerel: 0 ebeveyn: 0 gozlem: 0 ortam: 2
Üst değerin değişimi yalnız ArticlePane’i, ezilen değerin değişimi yalnız SidePane’i yeniden hesaplattı. İki satır da ortam. Ama bu sonucu ölçüm değil, kurduğumuz iki anahtar belirledi: ağaçta SidePane’e textScale okuması yazsaydık, model ezmeyi tanımadığı için üst değişimde onu da yeniden hesaplatırdı. Yani model ezmeyi temsil edebilir, ama onu ayırt edemez. Gerçek çerçevede ezmenin üst değeri gölgelediği, belgelenmiş bir davranıştır; bu derste ölçülen bir şey değildir.
DR12. Bu ölçüm yalnız iki anahtarlı bir temsil içindir. Aynı anahtarın üst ve alt ağaçta iki değerle okunduğu durum, ezmenin birden çok katmanda iç içe geçtiği durum ve ezilen değerin üst değerden türetildiği durum ölçülmedi.
Özet
- Ortam değeri ağacın bir noktasında konur; altındaki okuyan görünüm onu aradaki görünümlere dokunmadan okur.
- Değer değişince yalnız onu okuyan görünüm yeniden hesaplanır, tetik ortamdır; okumayan ara görünüm atlanır.
- Sistemin ortam değeri ile özel
@Entryanahtarı aynı tetiği üretir. - Ortama konan nesnenin özelliği değişince tetik ortam değil gözlemdir; ortam yalnız erişim yoludur.
- Alt ağaçta ezme, tek anahtarlı modelde ayrı bir anahtar olarak temsil edilir; model ezmeyi ayırt edemez.
- Bu dersin tetiği: ortam.
Sonraki Adım
Dört tetiğin dördü de bu noktada ölçüldü ve hepsinde değişen şey bir değerdi. Sıradaki derste ekranın kendisi de bir değere bağlanır: hangi ekranın görüneceğini gösteren bir yol. Gezinme yığıtı ve yolu bir durumdur; bu durumun değişimi hangi görünümleri yeniden hesaplatır sorusu, yine aynı tetik sorusudur.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.