Ders 03 / 21
Yığıt Düzenleri
Yığıtın aralık ve eksen değerlerinin ebeveyn durumundan çocuklara indiğinin, yön değerinin ortamdan okunduğunun ve derinlik yığıtında kardeş değerinin okunamadığının ölçümü.
Yayın Güncelleme
Bu derste
- Yatay, dikey ve derinlik yığıtlarının çocukları nasıl dizdiğini kaynak parçada okur
- Yığıtın aralık ve eksen değerlerinin çocuklara ebeveyn durumu olarak indiğini ölçer
- Yazı yönü gibi ortamdan okunan değerin yığıt değerinden farklı bir tetik ürettiğini gösterir
- Derinlik yığıtında üstteki görünümün alttakinin değerini okuyamadığını ölçer
İçindekiler
Önceki derste bir değiştiricinin etkisinin alt ağaca ortam değeri olarak aktığını ölçtük. Bu derste aynı ağaçta çocukları dizen kapsayıcıya bakıyoruz: yığıt. Yığıt, çocuklarını yatayda, dikeyde ya da üst üste dizer ve aralarındaki boşluğu, hizalamayı kendi parametreleriyle belirler. Bu parametreler yığıtın sahibi olan görünümün durumundan gelir. Soru aynı kalır: bu yeniden hesaplamayı ne tetikledi? Bu derste bakılan tetik, ebeveyn.
// 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 araç çubuğudur: çubuğu kuran ekran, çubuğu tutan yığıt ve içindeki simge, etiket ve rozet.
Üç Yığıt, Üç Eksen
Yatay yığıt çocukları soldan sağa dizer, dikey yığıt yukarıdan aşağıya, derinlik yığıtı ise aynı alana üst üste koyar. İlk ikisinde aralık çocuklar arasındaki boşluğu, hizalama çocukların çapraz eksendeki konumunu belirler; üçüncüsünde yalnız hizalama vardır, çünkü çocuklar yan yana değil üst üste durur. Aşağıdaki parça üçünü bir arada gösterir.
// Kaynak parca: yatay, dikey ve derinlik yigiti
import SwiftUI
struct ToolbarStack: View {
let spacing: Double
let isVertical: Bool
var body: some View {
VStack(spacing: spacing) {
HStack(spacing: spacing) {
Image(systemName: "pencil")
Text("Duzenle")
}
ZStack(alignment: isVertical ? .topTrailing : .bottomTrailing) {
Image(systemName: "bell")
Text("3")
}
}
}
}
spacing ve isVertical saklanan değer değil, parametredir; yığıtı kuran görünüm ikisini de bir üst görünümden almıştır. Rozet ekseni okur: hizalaması isVertical değerine göre seçilir. Yığıtın içindeki çocuklar ise aralığı kendileri okumaz, yığıt aralığı çocukların arasına koyar. Bu ders modeli biraz sadeleştirir: yığıtın aralığına uyan her çocuk, aralığın değişmesiyle yeniden hesaplanan bir okuyucu sayılır. Bunun yaptığı şey, ebeveyn tetiğini görünür kılmaktır; çerçevenin aralığı çocuklara gerçekte nasıl ilettiğini göstermez.
GV7. Bu ölçüm yığıtın aralık ve eksen değerlerini çocuk görünümlerin okuduğu değerler olarak modeller. Çerçevenin yığıt değişince çocukları yeniden hesaplayıp hesaplamadığı ya da yalnız yerleşimi güncelleyip güncellemediği ölçülmedi.
Yığıttan Çocuğa İnen Değer
Bir yığıtın aralığı ve ekseni, yığıtı kuran görünümün durumundan gelir. Çocuk bu değeri okuyorsa, değerin sahibi çocuğun bir atasıdır ve model tetiği ebeveyn olarak sınıflar. Aşağıdaki ağaçta iki değerin sahibi yığıtı kuran ToolbarScreen’dir; ToolbarStack ikisini de let parametresi olarak alır. Aralığı simge ve etiket, ekseni (isVertical) rozet okur. Yazı yönü ise ortam değeridir ve etiketle rozet tarafından okunur.
// main.swift — yigitin degerleri ebeveyn, yon degeri ortam tetigi uretir
let storage: [String: Storage] = [
"spacing": .state(owner: "ToolbarScreen"),
"isVertical": .state(owner: "ToolbarScreen"),
"layoutDirection": .environment,
]
let screen = ViewTree(nodes: [
ViewNode("ToolbarScreen", parent: nil, reads: ["spacing", "isVertical"]),
ViewNode("ToolbarStack", parent: "ToolbarScreen", reads: ["spacing", "isVertical"]),
ViewNode("ToolIcon", parent: "ToolbarStack", reads: ["spacing"]),
ViewNode("ToolLabel", parent: "ToolbarStack", reads: ["spacing", "layoutDirection"]),
ViewNode("ToolBadge", parent: "ToolbarStack", reads: ["isVertical", "layoutDirection"]),
], storage: storage)
let recomputes = [
screen.change("aralik-degisti", key: "spacing"),
screen.change("eksen-degisti", key: "isVertical"),
screen.change("yon-degisti", key: "layoutDirection"),
].flatMap { $0 }
countCauses(recomputes)
aralik-degisti: spacing degisti | yeniden ToolbarScreen yerel, ToolbarStack ebeveyn, ToolIcon ebeveyn, ToolLabel ebeveyn | atlanan ToolBadge eksen-degisti: isVertical degisti | yeniden ToolbarScreen yerel, ToolbarStack ebeveyn, ToolBadge ebeveyn | atlanan ToolIcon,ToolLabel yon-degisti: layoutDirection degisti | yeniden ToolLabel ortam, ToolBadge ortam | atlanan ToolbarScreen,ToolbarStack,ToolIcon yerel: 2 ebeveyn: 5 gozlem: 0 ortam: 2
aralik-degisti satırında değerin sahibi olan ekran yerel; yığıt, simge ve etiket ebeveyn tetiğiyle yeniden hesaplandı; rozet aralığı okumadığından atlandı. eksen-degisti satırında ekran yine yerel, yığıt ile rozet ebeveyn tetiğiyle yeniden hesaplandı; simge ve etiket atlandı. yon-degisti satırında ekran ile yığıt atlandı, etiket ve rozet ortam tetiğiyle yeniden hesaplandı: yazı yönü yığıtı kuran ekranın durumu değil, ağaca dışarıdan giren bir değerdir.
Sayaç da aynı hikâyeyi özetler: iki yerel, beş ebeveyn, iki ortam. Beş ebeveyn tetiği, değerlerin sahibinin tek bir değer değiştirmesinin kaç çocuğu etkileyebileceğini gösterir. Aralığı değiştiren bir görünüm, aralığı okuyan her çocuğu da yeniden hesaplatır.
GV8. Bu ölçüm spacing, isVertical ve layoutDirection için. Bir değerin hem yığıta hem de çocuğa parametre olarak geçtiği, aynı anahtarın iki yoldan okunduğu durum ölçülmedi. Değerlerin sahibi olarak eklenen ekran bir model varsayımıdır; kaynak parça yalnız yığıtın parametrelerini gösterir.
Yön ve Eksen Seçimi
Yığıtın eksenini çalışma anında seçmek mümkündür: yatay ve dikey düzen arasında geçiş yapan bir düzen değeri, çocukları aynı kalırken eksenini değiştirir. Aşağıdaki parça bunu kaynak olarak gösterir.
// Kaynak parca: ekseni calisma aninda secen duzen
import SwiftUI
struct AdaptiveToolbar: View {
let isVertical: Bool
var body: some View {
let layout = isVertical ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
layout {
Image(systemName: "pencil")
Text("Duzenle")
}
}
}
isVertical bu görünümün let parametresidir, değerin sahibi onu kuran görünümdür; değer değişince yeni bir düzen değeri kurulur ve çocuklar aynı kalır. Modelde bu, isVertical anahtarının değişmesiyle aynıdır: değerin sahibi ToolbarScreen yerel tetikle, yığıt ve eksene bağlı çocuklar ebeveyn tetiğiyle yeniden hesaplanır. Yön ise ortamdan gelir ve düzen değeriyle ilgisi yoktur; sağdan sola yazılan bir dilde yatay yığıt çocuklarını tersten dizer, ama bu, yığıtın sahibinin kararı değildir.
GV9. Eksen seçimi bu derste yalnız kaynak parçada ve isVertical anahtarının modeldeki karşılığıyla ele alındı. Düzen değerinin değişmesinin çocukların kimliğini koruyup korumadığı ölçülmedi.
Sınırlayıcı Ölçüm
Derinlik yığıtında çocuklar aynı alanı paylaşır, ama kaynakta biri ötekinin @State değerine ulaşamaz: @State görünüme özeldir ve kardeş onu ne okuyabilir ne yazabilir. Bu kısıt yığıttan değil, @State’in görünüme özel olmasından gelir; yatay ya da dikey yığıttaki kardeşler için de aynıdır. Model bu kaynak kısıtını kendi kuralıyla taklit eder: değerin sahibi okuyanın atası değilse okuma kural-disi sayılır. Aşağıdaki ağaç kaynakta yazılamayacak bu okumayı modelde bilerek kurar: fotoğraf katmanı kendi photoName değerinin sahibidir, bindirme katmanı da bu değeri okuyormuş gibi modellenir.
// main.swift — derinlik yiginindaki kardes, digerinin degerini okuyamaz
let storage: [String: Storage] = [
"photoName": .state(owner: "PhotoLayer"),
"caption": .state(owner: "PhotoOverlay"),
]
let screen = ViewTree(nodes: [
ViewNode("PhotoStack", parent: nil, reads: []),
ViewNode("PhotoLayer", parent: "PhotoStack", reads: ["photoName"]),
ViewNode("PhotoOverlay", parent: "PhotoStack", reads: ["caption", "photoName"]),
], storage: storage)
let recomputes = [
screen.change("foto-degisti", key: "photoName"),
screen.change("yazi-degisti", key: "caption"),
].flatMap { $0 }
countCauses(recomputes)
foto-degisti: photoName degisti | yeniden PhotoLayer yerel, PhotoOverlay kural-disi | atlanan PhotoStack yazi-degisti: caption degisti | yeniden PhotoOverlay yerel | atlanan PhotoStack,PhotoLayer yerel: 2 ebeveyn: 0 gozlem: 0 ortam: 0
foto-degisti satırında fotoğraf katmanı yerel, bindirme kural-disi olarak işaretlendi. Model, değerin sahibinin okuyanın atası olmadığını görür ve bu okumayı kural dışı sayar; bu bir çerçeve davranışının ölçümü değil, modelin kurulmaması gereken bir okumayı yakalamasıdır. İki katmanın aynı değeri kullanması gerekiyorsa değer ortak ataya, burada PhotoStack’e taşınır; o zaman iki katman da onu ebeveyn tetiğiyle okur. yazi-degisti satırında yalnız bindirme yeniden hesaplandı.
Sayaçta kural dışı tetik görünmez, çünkü countCauses yalnız dört tetiği sayar: yerel iki, ötekiler sıfır. Bu, sayaçtaki toplamın her zaman yeniden hesaplama sayısına eşit olmadığını gösterir; sayılmayan tetikler ayrıca okunmalıdır.
GV10. Bu ölçüm iki kardeş görünüm ve tek bir paylaşılan anahtar için. Çerçevenin kardeşler arasında değer aktarma yollarını, örneğin tercih anahtarlarını, ölçmedik.
Özet
- Yığıt çocuklarını yatayda, dikeyde ya da üst üste dizer; aralık ve hizalama yığıtın parametreleridir.
- Yığıtı kuran görünümün durumundan gelen aralık ve eksen, yığıt ve çocuklar için ebeveyn tetiği üretir.
- Yazı yönü ortamdan gelir; yığıtın sahibinin durumu olmadığı için ortam tetiği üretir.
- Eksen seçimi, yığıtın sahibinin tek bir değeriyle yığıtı ve ona bağlı çocukları etkiler.
- Kardeşler, aynı alanı paylaşan derinlik yığıtında bile birbirinin
@Statedeğerini okuyamaz; kısıt@State’in görünüme özel olmasından gelir, model bu okumayı kural dışı sınıflar. - Bu dersin tetiği: ebeveyn.
Sonraki Adım
Bu derste yığıtın aralığı ve ekseni, sahibi olan ebeveynden çocuklara indi. Sıradaki derste aynı iniş boyutlar için işler: bir ebeveyn çocuğuna ne kadar yer önerdiğini nasıl belirler ve bu öneri hangi değere bağlıdır. Soru aynı kalır, ama bu kez değer bir aralık değil, bir boyut önerisidir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.