İçeriğe geç
academia.sh

Ders 19 / 21

Özellik Sarmalayıcıları

Özellik sarmalayıcısının wrappedValue okuyucusunun her erişimde çalıştığını, başlangıç değerinin bir kez kurulduğunu ve önbellekli bir sarmalayıcının anı ilk kullanıma çevirdiğini ölçer.

Yayın Güncelleme

Bu derste

  • Bir özellik sarmalayıcısını wrappedValue ve projectedValue ile yazar ve $ önekiyle yansıtılan değere erişir
  • wrappedValue okuyucusunun her erişimde çalıştığını ölçüm çıktısından okur
  • Sarmalayıcıya verilen başlangıç değerinin özellik kurulurken bir kez değerlendirildiğini ölçer
  • Önbellekli bir sarmalayıcıda değerlendirme anının sarmalayıcının gövdesinde yazıldığını gösterir
İçindekiler

Önceki derste kapanış, bir değeri kendi içine alıp saklayan bir kutuydu. Özellik sarmalayıcısı (@propertyWrapper) benzer bir kutuyu özelliğin kendisi için kurar: özelliğin değerinin nasıl saklanacağı, okunurken ya da yazılırken ne olacağı ayrı bir tipe taşınır ve özellik o tipin adıyla işaretlenir. Sarmalayıcı tipinin zorunlu tek üyesi wrappedValue’dur; özellik okunduğunda ve yazıldığında bu üye çalışır. İsteğe bağlı projectedValue üyesi ise özelliğin adının başına $ konarak erişilen ikinci bir değer sunar.

Bu ders bir sarmalayıcıyı yazar ve onun üç farklı anını ölçer: okuyucunun çalışma anı, başlangıç değerinin değerlendirme anı ve bir önbellek eklenince değişen an. Ölçümlerin çoğu bir Clamped sarmalayıcısını kullanır; değeri bir aralığa sıkıştıran küçük bir tip.

// Probe.swift — bir ifadenin ne zaman degerlendirildigini kaydeder
final class Probe {
    let label: String
    private(set) var evaluations = 0
    private(set) var uses = 0
    private var evaluatedBeforeUse = false

    init(_ label: String) {
        self.label = label
    }

    func eval<T>(_ value: T) -> T {
        evaluations += 1
        if uses == 0 { evaluatedBeforeUse = true }
        return value
    }

    func use<T>(_ access: () -> T) -> T {
        uses += 1
        return access()
    }

    var moment: String {
        if evaluations == 0 { return "hic" }
        if evaluations > 1 { return "her-kullanimda" }
        return evaluatedBeforeUse ? "hemen" : "ilk-kullanimda"
    }

    func report() {
        print("\(label): \(evaluations) degerlendirme, \(uses) kullanim -> \(moment)")
    }
}

func tally(_ probes: [Probe]) {
    for moment in ["hemen", "ilk-kullanimda", "her-kullanimda", "hic"] {
        let count = probes.filter { $0.moment == moment }.count
        print("\(moment): \(count)")
    }
}
// Clamped.swift — degeri bir araliga sikistirir
@propertyWrapper
struct Clamped {
    private var current: Int
    let range: ClosedRange<Int>
    let owner: Probe

    init(wrappedValue: Int, _ range: ClosedRange<Int>, owner: Probe) {
        self.range = range
        self.owner = owner
        self.current = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }

    var wrappedValue: Int {
        get { owner.eval(current) }
        set { current = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    var projectedValue: Bool { current == range.upperBound }
}

Sarmalayıcıyı Yazmak

Clamped sarmalayıcısı bir Int tutar ve atanan değeri verilen aralığa sıkıştırır. wrappedValue okuyucusunun gövdesi değeri döndürmeden önce owner.eval çağırır; okuyucunun her çalışışı böylece kaydedilir. projectedValue değerin üst sınırda olup olmadığını söyleyen bir Bool’dur ve okunurken eval çağırmaz, çünkü ölçülen şey wrappedValue’dur.

Okuyucu Her Erişimde Çalışır

Sarmalayıcı kullanılırken özellik @Clamped(0...10, owner: reads) var level = 5 biçiminde yazılır: parantez içindeki değerler sarmalayıcının init yöntemine, = 5 ise wrappedValue parametresine gider. İlk ölçüm level’ı iki kez okur ve arada sınırın dışında bir değer atar. Son satırda $level yansıtılan değeri okur.

// main.swift — wrappedValue okuyucusu her erisimde calisir
let reads = Probe("sarmalayici-okuma")

do {
    @Clamped(0...10, owner: reads) var level = 5
    print("seviye: \(reads.use { level })")
    level = 42
    print("seviye: \(reads.use { level })")
    print("ust sinirda: \($level)")
}

reads.report()
seviye: 5
seviye: 10
ust sinirda: true
sarmalayici-okuma: 2 degerlendirme, 2 kullanim -> her-kullanimda

İlk okuma 5, ikincisi 10 verir: atanan 42 aralığın üst sınırına sıkıştırıldı. $level ise projectedValue’yu okur ve değer üst sınırda olduğu için true yazar. sarmalayici-okuma iki kullanım ve iki değerlendirme kaydeder; okuyucu her erişimde çalıştığı için anı her-kullanimda olur. $level okuması kaydı artırmadı, çünkü o başka bir üyeyi çağırır. Bu dersin anı her kullanımdadır.

Başlangıç Değeri Bir Kez Kurulur

Özelliğe yazılan = 5 ifadesi okuyucunun her çalışmasında değil, özellik kurulurken bir kez değerlendirilir. İkinci ölçüm başlangıç değerini start.eval(5) ile sarar ve özelliği iki kez okur. Okuyucunun kendi eval çağrısı farklı bir Probe’a, quiet’e yazar; böylece start yalnızca başlangıç değerinin kaydını tutar.

// main.swift — baslangic degeri ozellik kurulurken bir kez degerlendirilir
let start = Probe("baslangic-degeri")
let quiet = Probe("sessiz-okuma")

do {
    @Clamped(0...10, owner: quiet) var level = start.eval(5)
    print("seviye: \(start.use { level }) \(start.use { level })")
}

start.report()
seviye: 5 5
baslangic-degeri: 1 degerlendirme, 2 kullanim -> hemen

İki okuma da 5 verir ve baslangic-degeri tek değerlendirme kaydeder. Bu değerlendirme ilk use çağrısından önce olduğu için anı hemendir. Okuyucu iki kez çalıştı ama başlangıç ifadesini yeniden değerlendirmedi: sarmalayıcının init yöntemi ifadenin değerini bir kez aldı ve current’a yazdı.

// Cached.swift — degeri ilk okumada hesaplar ve saklar
@propertyWrapper
final class Cached {
    private var memo: Int?
    private let make: () -> Int

    init(wrappedValue make: @autoclosure @escaping () -> Int) {
        self.make = make
    }

    var wrappedValue: Int {
        if let memo { return memo }
        let result = make()
        memo = result
        return result
    }
}

Yazıcı da Her Atamada Çalışır

wrappedValue iki yönlüdür: get okumada, set atamada çalışır. Başlangıç değeri ise set üzerinden geçmez; init yöntemi onu doğrudan saklar. Bu fark yazma tarafında ayrıca ölçülebilir. Counted sarmalayıcısı set gövdesinde owner.eval çağırır ve hiçbir sınırlama yapmaz. Ölçüm özelliği 0 ile başlatır, sonra üç kez artırır.

// Counted.swift — her atamayi kaydeder
@propertyWrapper
struct Counted {
    private var current: Int
    let owner: Probe

    init(wrappedValue: Int, owner: Probe) {
        self.current = wrappedValue
        self.owner = owner
    }

    var wrappedValue: Int {
        get { current }
        set { current = owner.eval(newValue) }
    }
}
// main.swift — set her atamada calisir, baslangic degeri set'ten gecmez
let writes = Probe("sarmalayici-yazma")

do {
    @Counted(owner: writes) var score = 0
    for amount in 1...3 {
        _ = writes.use { score += amount }
    }
    print("puan: \(score)")
}

writes.report()
puan: 6
sarmalayici-yazma: 3 degerlendirme, 3 kullanim -> her-kullanimda

Puan 6 olur, çünkü 1, 2 ve 3 eklendi. sarmalayici-yazma üç kullanım ve üç değerlendirme kaydeder: başlangıçtaki = 0 ataması değerlendirmeye girmedi, yalnızca üç artırma girdi. Okuma ve yazma tarafları aynı kuralı izler: sarmalayıcının üyesi her erişimde çalışır, başlangıç değeri ise init yönteminde bir kez alınır.

Yansıtılan Değer

Clamped iki şey sunar: wrappedValue ile özelliğin kendisi, projectedValue ile yanında taşınan ek bir bilgi. Ek bilgiye özelliğin adının başına $ koyarak ulaşılır. Bu $ okuması wrappedValue okuyucusunu çağırmaz; ayrı bir özelliktir. Ölçüm iki aynı sarmalayıcıyı ayrı Probe’larla kurar, ikisine de aralığın dışında bir değer atar. Birinden $ ile, ötekinden doğrudan okur.

// main.swift — $ okumasi wrappedValue okuyucusunu cagirmaz
let projected = Probe("yansitilan-deger")
let plain = Probe("dogrudan-okuma")
do {
    @Clamped(0...10, owner: projected) var left = 5
    @Clamped(0...10, owner: plain) var right = 5
    left = 50
    right = 50
    print("ust sinirda: \(projected.use { $left }) \(projected.use { $left })")
    print("deger: \(plain.use { right }) \(plain.use { right })")
}
projected.report()
plain.report()
ust sinirda: true true
deger: 10 10
yansitilan-deger: 0 degerlendirme, 2 kullanim -> hic
dogrudan-okuma: 2 degerlendirme, 2 kullanim -> her-kullanimda

İki özelliğe de 50 atandı ve ikisi de 10’a sıkıştırıldı. $left okuması true verir: değer üst sınırdadır. Ama bu okuma wrappedValue okuyucusuna uğramaz. yansitilan-deger iki kullanıma karşılık sıfır değerlendirme kaydeder ve hic der; çünkü projectedValue gövdesinde eval yoktur ve current doğrudan karşılaştırılır. dogrudan-okuma ise aynı iki kullanımda iki değerlendirme kaydeder ve her-kullanimda der. $ okuması wrappedValue okuyucusunu çalıştırmaz; ayrı bir üyedir: $ yazımı sarmalayıcıya, wrappedValue okuyucusunun yan etkilerinden bağımsız bir soru sorar. Anı sorusu bu yüzden her erişim için ayrı sorulur: left ve right aynı sarmalayıcı tipidir, ama hangisinin okuyucusunun çalıştığı erişimin yazılışına bağlıdır.

Sarmalayıcı Argümanları Bir Kez Değerlendirilir

@Clamped(0...10, owner: quiet) yazımındaki parantezli ifadeler sarmalayıcının init yöntemine gider. Derleyici @Clamped(...) var level = 5 satırını, kabaca, özellik kurulurken bir kez çalışan bir Clamped(wrappedValue: 5, 0...10, owner: quiet) çağrısına çevirir. Bu ölçüm hem özellik biçimini hem de elle yazılmış init çağrısını sınar. Aralığın üst sınırı ve başlangıç değeri Probe içinden geçer.

// main.swift — sarmalayici argumanlari ozellik kurulurken bir kez degerlendirilir
let ceiling = Probe("aralik-argumani")
let explicit = Probe("acik-baslatma")
let quiet = Probe("sessiz")
do {
    @Clamped(0...ceiling.eval(10), owner: quiet) var level = 5
    print("seviye: \(ceiling.use { level }) \(ceiling.use { level })")

    let wrapper = Clamped(wrappedValue: explicit.eval(5), 0...10, owner: quiet)
    print("acik: \(explicit.use { wrapper.wrappedValue }) \(explicit.use { wrapper.wrappedValue })")
}
ceiling.report()
explicit.report()
seviye: 5 5
acik: 5 5
aralik-argumani: 1 degerlendirme, 2 kullanim -> hemen
acik-baslatma: 1 degerlendirme, 2 kullanim -> hemen

İki yolda da sayaç aynı şeyi söyler. aralik-argumani tek değerlendirme kaydeder ve bu değerlendirme ilk use çağrısından önce olduğu için anı hemendir. İki okuma aralığın üst sınırını yeniden hesaplamadı: ceiling.eval(10) özellik kurulurken bir kez çalıştı ve ClosedRange değeri sarmalayıcının içinde kaldı. Elle yazılmış Clamped(wrappedValue: explicit.eval(5), ...) çağrısı da aynı sonucu verir: acik-baslatma tek değerlendirme kaydeder ve hemen der. Sarmalayıcı sözdizimi yeni bir değerlendirme kuralı getirmez; bir init çağrısını kısa yazar ve çağrının argümanları her çağrıda olduğu gibi çağrı anında bir kez değerlendirilir. Okuyucunun kaç kez çalıştığı ile argümanın kaç kez değerlendirildiği iki ayrı sayıdır: ilki her erişimle artar, ikincisi özellik kurulurken bir kez sabitlenir. Bu ayrım, sarmalayıcıyı okurken hangi sayıya bakıldığını baştan seçmeyi gerektirir.

Sarmalayıcı Bileşimi

Bir özelliğe birden çok sarmalayıcı yazılabilir. Soldakiler dıştadır, sağdakiler içte. Aşağıdaki Traced genel bir sarmalayıcıdır; değeri olduğu gibi saklar ve her okumada owner.eval çağırır. Ölçüm onu Clamped’ın dışına koyar. Özelliği iki kez okur ve her iki katmanın okuyucusunun kaç kez çalıştığına bakar.

// Traced.swift — her okumayi kaydeden genel sarmalayici
@propertyWrapper
struct Traced<Value> {
    private var current: Value
    let owner: Probe

    init(wrappedValue: Value, owner: Probe) {
        self.current = wrappedValue
        self.owner = owner
    }

    var wrappedValue: Value {
        get { owner.eval(current) }
        set { current = newValue }
    }
}
// main.swift — iki sarmalayici ust uste
let outer = Probe("dis-sarmalayici")
let inner = Probe("ic-sarmalayici")
do {
    @Traced(owner: outer) @Clamped(0...10, owner: inner) var level = 50
    print("okuma: \(outer.use { inner.use { level } }) \(outer.use { inner.use { level } })")
    level = 3
    print("yazma sonrasi: \(outer.use { inner.use { level } })")
}
outer.report()
inner.report()
okuma: 10 10
yazma sonrasi: 3
dis-sarmalayici: 4 degerlendirme, 3 kullanim -> her-kullanimda
ic-sarmalayici: 3 degerlendirme, 3 kullanim -> her-kullanimda

İlk iki okuma 10 verir: başlangıç değeri 50 idi ve en içteki Clamped onu init içinde 10’a sıkıştırmıştı. level = 3 atamasından sonra okuma 3 verir. Sayaçlar dikkat çekicidir: iç sarmalayıcının okuyucusu üç okumada üç kez çalışır. Dış sarmalayıcının okuyucusu ise dört kez çalışır: üç okuma ve atamanın kendisi. Atama, dıştaki sarmalayıcının değerini önce okuyup içteki Clamped üzerinde değiştirdiği için dış okuyucuyu bir kez daha çağırır. Bileşim bu yüzden iki katmanın her birinin çalışma sayısını ayrı ayrı sayar. İki Probe de her-kullanimda der. Katman eklemek, her okuma ve yazmaya bir get ve set zinciri eklemektir. Okuyucusu yan etki taşıyan bir sarmalayıcıyı bileşime sokarken bu ek çalışmaların hesaba katılması gerekir. Bu ölçümde dış okuyucu dört, iç okuyucu üç kez koştu; daha çok katmanlı bileşim ölçülmedi.

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

Başlangıç değerinin hemen olması sarmalayıcı sözdiziminin değil, Clamped’ın yazılışının sonucudur. Aşağıdaki Cached sarmalayıcısı init yöntemine verilen ifadeyi @autoclosure ile bir kapanışa çevirir ve wrappedValue ilk okunana kadar çağırmaz. Sonuç bir özelliğe saklanır; sonraki okumalar saklanan değeri döndürür. Aynı @ işareti, aynı = ifade yazımı, ama değerlendirme anı değişti.

// main.swift — onbellekli sarmalayici ani ilk kullanima cevirir
let lazyValue = Probe("onbellekli-sarmalayici")

do {
    @Cached var total = lazyValue.eval(7)
    print("toplam: \(lazyValue.use { total }) \(lazyValue.use { total })")
}

lazyValue.report()
toplam: 7 7
onbellekli-sarmalayici: 1 degerlendirme, 2 kullanim -> ilk-kullanimda

İki okuma da 7 verir. onbellekli-sarmalayici tek değerlendirme kaydeder, ama bu kez değerlendirme ilk use çağrısının içinde olduğu için anı ilk-kullanimdadır. @Cached yazımı @Clamped yazımına benziyordu; farkı yaratan, ifadenin sarmalayıcının init yönteminde hemen kullanılması ya da bir kapanışta bekletilmesidir. An, @ sözdiziminde değil sarmalayıcının gövdesinde yazılıdır.

BL4. Ölçülen iki sarmalayıcı da Int değer tutar. Genel tipli sarmalayıcıların ve sınıf özelliği olarak kullanılan sarmalayıcıların aynı kuralları izlediği bu derste ölçülmedi. Yerel değişken olarak kullanım tasarım tercihidir.

Özet

  • Özellik sarmalayıcısı @propertyWrapper ile işaretlenir; wrappedValue özellik okunurken ve yazılırken çalışır, projectedValue $ önekiyle okunur.
  • wrappedValue okuyucusu her erişimde koşar; atama sarmalayıcının kuralına göre yapılır, örneğin aralığa sıkıştırılır.
  • Özelliğe yazılan başlangıç değeri Clamped gibi bir sarmalayıcıda özellik kurulurken bir kez değerlendirilir.
  • @autoclosure ve saklanan sonuçla yazılmış Cached aynı yazımı ilk kullanımda tek değerlendirmeye çevirir; an sarmalayıcının gövdesindedir.
  • Bu dersin anı: her kullanımda. wrappedValue okuyucusu her erişimde çalışır.

Sonraki Adım

Bu derste sarmalayıcı bir özelliğin değer kuralını ayrı bir tipe taşıdı. Sonuç kurucular aynı fikri bir ifade listesine uygular: bir blok içindeki satırlar tek tek değerlendirilmek yerine bir kurucu tipin buildBlock gibi yöntemlerine verilir ve hangi satırın hangi anda çalışacağını o tip belirler. Sonraki ders kendi sonuç kurucumuzu yazar ve seçilmeyen dalın ifadelerinin hiç değerlendirilmediğini ölçer.

İ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