Ders 02 / 21
Görünümler ve Değiştiriciler
Kapsayıcıya konan yazı tipi ve renk değiştiricilerinin alt ağaca ortam değeri olarak yayıldığının, değiştirici sırasının sonucu değiştirdiğinin ve kendi yazı tipini koyan görünümün ortamı okumadığının ölçümü.
Yayın Güncelleme
Bu derste
- Görünüm ile değiştiricinin farkını, değiştiricinin yeni bir görünüm döndürmesi üzerinden ayırır
- Kapsayıcıya konan yazı tipi ve renk değiştiricisinin alt ağaca ortam değeri olarak yayıldığını ölçer
- Ebeveynin parametreyle verdiği değer ile ortamdan okunan değerin tetiklerini ayırır
- Değiştirici sırasının sonucu nasıl değiştirdiğini kaynak parçada okur
İçindekiler
Önceki derste altı değişiklik dört tetik üretti ve bir ağacın okuma kümelerinden yeniden hesaplamaların çıkarıldığını gördük. Bu derste ağaca ilk kez dışarıdan bakmayı bırakıp görünümü kuran araca bakıyoruz: değiştirici. Bir görünüme .font, .padding ya da .foregroundStyle eklemek, o görünümü değiştirmez; eklenen her değiştirici, görünümü saran yeni bir görünüm değeri döndürür. Bazı değiştiriciler yalnız o görünümün görünüşünü etkiler, bazıları ise altındaki bütün ağaca yayılır. Soru aynı kalır: bu yeniden hesaplamayı ne tetikledi? Bu derste bakılan tetik, ortam.
// 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 profil kartıdır: kartı kuran ekran, kartın kendisi, bir başlık, bir gövde metni ve bir alt bilgi.
Alt Ağaca Yayılan Değiştirici
Yazı tipi ve ön plan rengi, bir görünümün değil, bir alt ağacın özelliğidir. Bir kapsayıcıya .font(.title2) konduğunda içindeki bütün metinler o yazı tipini kullanır; metnin kendisi bu değeri bir parametre olarak almamıştır, ortamdan okur. Bu, ortam değeri (environment value) mekanizmasıdır: değer ağaçta yukarıdan aşağıya akar ve okuyan her görünüm onu gövdesinde kullanır. Aşağıdaki parça bunu kaynak olarak gösterir.
// Kaynak parca: kapsayiciya konan font ve renk alt agaca yayilir
import SwiftUI
struct ProfileCard: View {
let headline: String
let isDimmed: Bool
var body: some View {
VStack(alignment: .leading) {
Text(headline)
Text("Yazilim muhendisi")
Text("Son gorulme: bugun")
}
.font(.title2)
.foregroundStyle(.secondary)
.opacity(isDimmed ? 0.5 : 1)
}
}
Üç metnin hiçbirine yazı tipi verilmedi; üçü de kapsayıcının .font değerini okur. .opacity ise ortam değeri olarak alt ağaca yayılmaz, kapsayıcının çiziminde uygulanır ve bu parçada kartın parametresinden gelir. Model bu iki yolu ayırır: ortamdan okunan değer ortam, ebeveynin verdiği değer ebeveyn tetiği üretir. Ağaçta font ve foregroundStyle ortam değeri, headline ve isDimmed ise kartı kuran ekranın, ProfileScreen’in durumu olarak saklanır; kart bu iki değeri let parametresi olarak alır, sahibi değildir.
// main.swift — kapsayiciya konan degisiklik alt agaca ortam olarak yayilir
let storage: [String: Storage] = [
"font": .environment,
"foregroundStyle": .environment,
"headline": .state(owner: "ProfileScreen"),
"isDimmed": .state(owner: "ProfileScreen"),
]
let screen = ViewTree(nodes: [
ViewNode("ProfileScreen", parent: nil, reads: ["headline", "isDimmed"]),
ViewNode("ProfileCard", parent: "ProfileScreen", reads: ["headline", "isDimmed"]),
ViewNode("CardTitle", parent: "ProfileCard", reads: ["headline", "font"]),
ViewNode("CardBody", parent: "ProfileCard", reads: ["font", "foregroundStyle"]),
ViewNode("CardFooter", parent: "ProfileCard", reads: ["font"]),
], storage: storage)
let recomputes = [
screen.change("yazi-tipi-degisti", key: "font"),
screen.change("baslik-degisti", key: "headline"),
screen.change("yazi-rengi-degisti", key: "foregroundStyle"),
screen.change("soluk-yapildi", key: "isDimmed"),
].flatMap { $0 }
countCauses(recomputes)
yazi-tipi-degisti: font degisti | yeniden CardTitle ortam, CardBody ortam, CardFooter ortam | atlanan ProfileScreen,ProfileCard baslik-degisti: headline degisti | yeniden ProfileScreen yerel, ProfileCard ebeveyn, CardTitle ebeveyn | atlanan CardBody,CardFooter yazi-rengi-degisti: foregroundStyle degisti | yeniden CardBody ortam | atlanan ProfileScreen,ProfileCard,CardTitle,CardFooter soluk-yapildi: isDimmed degisti | yeniden ProfileScreen yerel, ProfileCard ebeveyn | atlanan CardTitle,CardBody,CardFooter yerel: 2 ebeveyn: 3 gozlem: 0 ortam: 4
Dört değişiklik dokuz yeniden hesaplama üretti: iki yerel, üç ebeveyn, dört ortam. yazi-tipi-degisti satırında üç görünüm ortam tetiğiyle yeniden hesaplandı, ekran ile kart atlandı; kart font değerini okumuyor, yalnız altındakilere ortamla ulaştırıyor. baslik-degisti satırında değerin sahibi olan ekran yerel; kart ile başlık aynı değeri parametre olarak aldıkları için ebeveyn; gövde metni ve alt bilgi başlığı okumadığından atlandı. soluk-yapildi satırında ekran yerel, kart ebeveyn tetiğiyle yeniden hesaplandı: isDimmed değerini ekranın altında yalnız kartın gövdesi okur, içindeki metinler bu değerle ilgilenmez.
Aynı anahtarı iki farklı yoldan okumak da mümkündür, ama bu ağaç onu yapmıyor: font ortamdan, headline parametreyle gelir. İki yolun tetikleri ayrıdır. Ortam yolunda değeri veren, görünümün üstündeki bir değiştirici ya da sistemdir; ebeveyn yolunda değeri veren, doğrudan üst görünümdür.
GV4. Bu ölçüm font, foregroundStyle, headline ve isDimmed anahtarları için. font anahtarının değişmesi bir üst değiştiricinin yeni değer vermesi ya da sistem ayarı olarak okunur; hangisi olduğu ölçülmedi. Kart iki değeri let parametresi olarak aldığı için onların sahibi değildir; değer kartın kendi @State alanı olsaydı kart yerel tetikle yeniden hesaplanırdı, bu durum burada ölçülmedi. Bu dersin iki ağacında da değerlerin sahibi olarak eklenen ekran bir model varsayımıdır; kaynak parçalar yalnız görünümlerin parametrelerini gösterir.
Değiştirici Sırası
Bir değiştirici sarmalayıcı döndürdüğü için sıra anlam taşır: sonra gelen değiştirici, öncekinin döndürdüğü görünümü sarar. .padding() önce, .background() sonra gelirse arka plan boşluk eklenmiş görünümü kaplar; sıra tersine çevrilirse arka plan yalnız metni kaplar, boşluk dışarıda kalır. Aşağıdaki iki parça aynı iki değiştiriciyi iki sırayla verir.
// Kaynak parca: padding once, background sonra
import SwiftUI
struct PaddedBadge: View {
var body: some View {
Text("Yeni")
.padding()
.background(Color.yellow)
}
}
// Kaynak parca: background once, padding sonra
import SwiftUI
struct TightBadge: View {
var body: some View {
Text("Yeni")
.background(Color.yellow)
.padding()
}
}
İlkinde sarı alan metnin çevresindeki boşluğu da kaplar, ikincisinde yalnız metnin kendisini. Bu fark bir yeniden hesaplama farkı değildir; iki görünümde de aynı anahtarlar okunur ve aynı değişiklikler aynı görünümleri yeniden hesaplatır. Sıra, görünüşü belirler, tetiği belirlemez. Bu yüzden ağaçta padding ya da background gibi değiştiriciler düğüm değildir; düğümler, değerleri okuyan görünümlerdir. Model, çerçevenin bu kuralını kurar: görünüşü değiştiren bir değiştirici, okuduğu değer ağaçta bir anahtarsa devreye girer; aksi halde modelde görünmez.
İki parça da KAYNAK’tır; yerleşim sonuçları platformun çizim motoruna aittir ve ölçülmedi. Sarı alanın hangisinde nereye kadar uzandığı, bu derste kaynak okumasıdır.
GV5. Değiştirici sırası bu derste yalnız iki parçada ve yalnız padding ile background için okundu. Üç ve daha çok değiştiricinin sıra kombinasyonları, frame ve offset gibi boyutu etkileyenlerin sırası ölçülmedi.
Sınırlayıcı Ölçüm
Ortam değeri alt ağaçtaki her görünüme ulaşır, ama yalnız onu okuyan görünüm değer değişince yeniden hesaplanır. Bir çocuk kendi yazı tipini koyduğunda, kapsayıcının .font değerini artık kullanmaz. Aşağıdaki ağaç bunu kurar: CardTitle ortamdan yazı tipini okur, CardNote ise kendi .font değiştiricisini koyduğu için artık font anahtarını okumaz, yalnız note değerini okur.
// main.swift — kendi fontunu koyan gorunum ortam anahtarini okumaz
let storage: [String: Storage] = [
"font": .environment,
"headline": .state(owner: "ProfileScreen"),
"note": .state(owner: "ProfileScreen"),
]
let screen = ViewTree(nodes: [
ViewNode("ProfileScreen", parent: nil, reads: ["headline", "note"]),
ViewNode("ProfileCard", parent: "ProfileScreen", reads: ["headline", "note"]),
ViewNode("CardTitle", parent: "ProfileCard", reads: ["headline", "font"]),
ViewNode("CardNote", parent: "ProfileCard", reads: ["note"]),
], storage: storage)
let recomputes = [
screen.change("yazi-tipi-degisti", key: "font"),
screen.change("not-degisti", key: "note"),
].flatMap { $0 }
countCauses(recomputes)
yazi-tipi-degisti: font degisti | yeniden CardTitle ortam | atlanan ProfileScreen,ProfileCard,CardNote not-degisti: note degisti | yeniden ProfileScreen yerel, ProfileCard ebeveyn, CardNote ebeveyn | atlanan CardTitle yerel: 1 ebeveyn: 2 gozlem: 0 ortam: 1
yazi-tipi-degisti satırında CardNote atlanan listesinde: kapsayıcının yazı tipi değişti, ama not kendi yazı tipini koyduğundan o değeri okumuyor. not-degisti satırında değerin sahibi olan ekran yerel, kart ile not ebeveyn; CardTitle notu okumadığından atlandı. Yayılan bir değiştirici bu yüzden bir garanti değildir: ortam değerini okumayan, ortam değeri değişince yeniden hesaplanmaz.
Kaynakta karşılığı basittir. Not, kapsayıcıdan gelen yazı tipini kendi değiştiricisiyle ezer; bu, bir ortam anahtarını tüketmeyi bırakmaktır.
// Kaynak parca: kendi font'unu koyan gorunum kapsayicinin fontunu kullanmaz
import SwiftUI
struct CardNote: View {
let note: String
var body: some View {
Text(note)
.font(.caption)
}
}
Bu da KAYNAK’tır. Çerçevenin kapsayıcıdan gelen değeri ezme sırasını, yani en yakın değiştiricinin kazandığını bu derste ölçmedik; yalnız modeldeki okuma kümesinin sonucunu ölçtük.
GV6. Bu ölçüm tek bir ortam anahtarı ve tek bir görünüm için. Ezmenin kısmi olduğu durum, örneğin kendi yazı tipi boyutunu koyup kapsayıcının ağırlığını kullanan bir görünüm, ölçülmedi.
Özet
- Değiştirici, görünümü değiştirmez; yeni bir görünüm döndürür ve sonra gelen değiştirici öncekini sarar.
- Kapsayıcıya konan yazı tipi ve renk, alt ağaca ortam değeri olarak yayılır; okuyan her görünüm ortam tetiğiyle yeniden hesaplanır.
- Ebeveynin parametreyle verdiği değer ortam değildir; ebeveyn tetiği üretir.
- Değiştirici sırası görünüşü belirler, tetiği belirlemez.
- Kendi yazı tipini koyan görünüm ortam anahtarını okumaz ve ortam değişince atlanır.
- Bu dersin tetiği: ortam.
Sonraki Adım
Bu derste değer ağaçta yukarıdan aşağıya aktı ve okuyan alıp kullandı. Sıradaki derste aynı akış görünümleri yan yana ya da üst üste dizen yığıtlarda aralık ve hizalama olarak karşımıza çıkar: bir yığıtın aralığını belirleyen değer ebeveynin durumudur. Soru aynı kalır, ama bu kez tetik, çocukları dizen kapsayıcının kendi değerinden gelir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.