İçeriğe geç
academia.sh

Ders 13 / 21

Özellikler

Saklanan, hesaplanan ve gözlemleyen özelliğin, lazy özelliğin ve static let değerinin ne zaman kurulduğunun ölçümü.

Yayın Güncelleme

Bu derste

  • Saklanan, hesaplanan ve gözlemleyen özelliğin değerini ne zaman kurduğunu ayırt eder
  • lazy özelliğin ilk okumada bir kez değerlendirildiğini ölçer
  • Varsayılan değer ile lazy arasındaki farkı örnek oluşturulurken görünen çıktıdan okur
  • Yapıdaki lazy özelliğin kopyayla birlikte çoğaldığını gösterir
İçindekiler

Önceki ders numaralandırmaların ilişkili değerinin durum kurulurken bir kez, hesaplanan özelliğinin ise her okunuşta değerlendirildiğini gösterdi. Aynı ayrım yalnız numaralandırmaya özgü değildir. Yapının, sınıfın ve numaralandırmanın özellikleri (properties) bir tipin verisini adlandırır ve her özellik kendi değerini kendi zamanında kurar. Bazısı örnek oluşturulurken, bazısı her okunuşta, bazısı ilk okunuşta.

Bu derste özelliğin dört çeşidi ölçülür. Saklanan özellik (stored property) bir değeri örneğin içinde tutar. Hesaplanan özellik (computed property) değer saklamaz, okunuşta bir ifade koşturur. Gözlemleyen özellik (observed property), değişiminden önce ve sonra willSet ile didSet gözlemcilerini (property observer) çalıştıran saklanan özelliktir. Tembel özellik (lazy property) ilk okunuşa kadar değerini kurmaz. Kursun ölçü ekseni bu derste de aynıdır: bu ifade ne zaman değerlendiriliyor? Dört an vardır: hemen, ilk kullanımda, her kullanımda, hiç. Probe sınıfı aşağıda olduğu gibi verilir.

Çekirdek

Her ders aynı çekirdeği kullanır. Probe bir ifadenin kaç kez değerlendirildiğini (eval) ve kaç kez kullanıldığını (use) sayar; moment özelliği bu iki sayıdan anı çıkarır. Açılış dersindeki bağlayıcı ölçümde sekiz ifadenin dağılımı 2/1/3/2 idi: ikisi hemen, biri ilk kullanımda, üçü her kullanımda, ikisi hiç. Bu derste yalnız kendi kesitini ölçeriz.

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

Üç Temel Çeşit

İlk ölçümde saklanan, hesaplanan ve gözlemleyen özellik yan yana durur. Rectangle yapısındaki width ve height saklanan, area ise hesaplanan özelliktir: bir değer tutmaz, her okunuşta çarpmayı yapar. Thermostat sınıfındaki target saklanan bir özelliktir ve willSet ile didSet gözlemcileri taşır. willSet değer yazılmadan önce gelecek değeri newValue adıyla, didSet yazıldıktan sonra eski değeri oldValue adıyla görür.

// main.swift — saklanan, hesaplanan ve gozlemleyen ozellik
struct Rectangle {
    var width: Int
    var height: Int

    var area: Int {
        width * height
    }
}

final class Thermostat {
    var target = 20 {
        willSet { print("willSet", target, "->", newValue) }
        didSet { print("didSet", oldValue, "->", target) }
    }

    init(target: Int) {
        self.target = target
    }
}

let rect = Rectangle(width: 3, height: 4)
print(rect.area)
let thermostat = Thermostat(target: 18)
print("baslatici bitti")
thermostat.target = 22
thermostat.target = 25
12
baslatici bitti
willSet 18 -> 22
didSet 18 -> 22
willSet 22 -> 25
didSet 22 -> 25

İlk satır area özelliğinin 3 * 4 sonucunu verir. İkinci satırdan önce başlatıcı target alanına 18 yazmıştı, ama gözlemciler çalışmadı: başlatıcı bitene kadar bir satır görünmedi. Gözlemciler yalnızca örnek tam kurulduktan sonraki atamalarda çalışır; tipin kendi başlatıcısındaki atama onları tetiklemez. Sonraki iki atama gözlemcileri çalıştırdı ve her atama için sıra willSet, atama, didSet oldu. willSet çıktısındaki sağ değer gelecek değeri, didSet çıktısındaki sol değer eski değeri gösterir; ikincisi target zaten güncellenmiş olduğundan, oldValue adı olmadan eski değere ulaşılamazdı.

Üç çeşit üç farklı soruya yanıt verir. Saklanan özellik “değer nerede duruyor?” sorusunu yanıtlar. Hesaplanan özellik “değer başka değerlerden nasıl türer?” sorusunu yanıtlar. Gözlemleyen özellik “değer değiştiğinde ne olmalı?” sorusunu yanıtlar. Hiçbiri tembel değildir: saklanan değer kurulurken, hesaplanan değer her okunuşta, gözlemci her yazımda çalışır.

TS18. Bu ölçüm bir yapıdaki hesaplanan, bir sınıftaki gözlemleyen özellik için. Yapıdaki gözlemciler, hesaplanan özelliğin değişim yönü ve gözlemcilerin inout ile geçirilen özellikte davranışı ayrıca ölçülmedi.

Hesaplanan Özellik ve set

Hesaplanan özellik yalnızca okunmaz, yazılabilir de. Bunun için get yanına bir set yazılır; set atanan değeri newValue adıyla alır ve bunu saklanan özelliklere yansıtmakla yükümlüdür. Temperature yapısında fahrenheit ne bir değer saklar ne de kendisi bir kaynaktır. Okunduğunda celsius’tan türer, yazıldığında celsius’u günceller.

// main.swift — hesaplanan ozellik get ve set ile saklanan alana baglanir
struct Temperature {
    var celsius: Double

    var fahrenheit: Double {
        get { celsius * 9 / 5 + 32 }
        set { celsius = (newValue - 32) * 5 / 9 }
    }
}

var reading = Temperature(celsius: 100)
print(reading.fahrenheit)
reading.fahrenheit = 32
print(reading.celsius)
212.0
0.0

celsius 100 iken fahrenheit okunduğunda 212.0 çıktı. Sonra fahrenheit’a 32 yazıldı ve set bloğu celsius’u 0.0’a çekti. Tek bir veri, iki ayrı gösterimle okunup yazılabiliyor ve gerçek değer tek bir yerde, celsius alanında duruyor. Hesaplanan özelliğin gücü budur: tutarsızlığa yol açmadan aynı bilginin iki ayrı biçimini sunar.

Önceki dersteki numaralandırma örneğinden aynı sonuç çıkar: hesaplanan özellik her okunuşta yeniden koşar. Burada fark, set ile geri yönde de çalışmasıdır.

Tembel Özellik

Bazı özellikler pahalıdır ve her örnekte gerekmez. Örnek oluşturulurken hesaplanmaları, hiç okunmayacak bir örnek için boşa gidecek iştir. lazy anahtar sözcüğü özelliği, ilk okunuşa kadar kurulmamış bırakır. İlk okuma ifadeyi bir kez değerlendirir, sonucu özelliğin içinde saklar ve sonraki okumalar saklı değeri verir. Report sınıfındaki summary özelliği satırların toplamını tembel olarak hesaplar.

// main.swift — lazy ozellik ilk okumada bir kez degerlendirilir
final class Report {
    let probe: Probe
    var rows = [1, 2, 3]
    lazy var summary: Int = probe.eval(rows.reduce(0, +))

    init(probe: Probe) {
        self.probe = probe
    }
}

let summaryProbe = Probe("lazy-ilk-okuma")
let report = Report(probe: summaryProbe)
_ = summaryProbe.use { report.summary }
_ = summaryProbe.use { report.summary }
summaryProbe.report()
lazy-ilk-okuma: 1 degerlendirme, 2 kullanim -> ilk-kullanimda

Bir değerlendirme, iki okuma, an ilk kullanımda. Hesaplama ilk okuyuşta yapıldı ve ikinci okuyuş saklı sonucu verdi. Bunu hesaplanan özellikten ayıran şey budur: hesaplanan özellik iki okuyuşta iki değerlendirme yapardı. lazy ise bir hesaplanan özellik değildir, ertelenmiş bir saklanan özelliktir. Bu yüzden lazy bir var olmak zorundadır; ilk okuma değeri özelliğe yazdığı için, yazılabilir bir saklanan özellik gerekir.

Varsayılan Değer ile lazy Arasındaki Fark

Önceki derste varsayılan değerin her örnekte bir kez değerlendirildiğini ölçtük. Şimdi aynı ifadeyi iki biçimde yazalım ve fark görünür olsun. Ticket sınıfı iki özellik taşır: eagerId bir varsayılan değerle, lazyId lazy ile aynı makeId işlevini çağırır ve işlev her çağrıldığında bir satır yazdırır. İki bilet oluşturulur, ardından yalnızca ilk biletin lazyId özelliği iki kez okunur.

// main.swift — varsayilan deger olusturmada, lazy ilk okumada
func makeId(_ kind: String) -> Int {
    print("uretildi", kind)
    return 7
}

final class Ticket {
    var eagerId = makeId("hemen")
    lazy var lazyId = makeId("tembel")
}

let tickets = [Ticket(), Ticket()]
print("olusturuldu")
print(tickets[0].lazyId)
print(tickets[0].lazyId)
uretildi hemen
uretildi hemen
olusturuldu
uretildi tembel
7
7

İlk iki satır, iki biletin eagerId özelliğinin olusturuldu yazılmadan önce kurulduğunu gösterir: her örnek kendi varsayılan değerini oluşturma anında hesapladı. lazyId için bu satırlar yok. tembel satırı ancak tickets[0].lazyId ilk okunduğunda göründü ve yalnız bir kez göründü; ikinci okuma 7’yi saklı değerden verdi. İkinci biletin lazyId özelliği hiç okunmadığı için hiç kurulmadı.

Fark kısaca şöyle özetlenir. Varsayılan değerin ifadesi her örnek için, örnek oluşurken koşar. lazy ifadesi, yalnızca okunan örnekler için, ilk okunuşta koşar. Yirmi bilet oluşturulup yalnızca birinin kimliğine bakılıyorsa lazy yirmi yerine bir hesap yapar.

static let

Tipe ait değerler, yani static özellikler, örneklere değil tipin kendisine bağlıdır. static let ile tanımlanan bir değer tembeldir: ilk erişimde kurulur ve bir kez kurulur. Config.version bir kapanışla oluşturulur ve kapanış her çalıştığında bir satır yazdırır.

// main.swift — static let ilk erisimde kurulur
enum Config {
    static let version: String = {
        print("static let kuruldu")
        return "1.0"
    }()
}

print("once")
print(Config.version)
print(Config.version)
once
static let kuruldu
1.0
1.0

once satırı ilk erişimden önce yazıldı; static let kuruldu satırı ancak Config.version ilk okunduğunda göründü ve yalnız bir kez göründü. Sonraki okuma 1.0 değerini saklı kopyadan verdi. Bu, lazy ile aynı davranıştır, ama bir fark vardır: static let bir örneğe bağlı değildir ve lazy gibi var olmak zorunda değildir, bu yüzden değişmez bir tembel sabit olarak kullanılabilir.

TS19. Bu ölçüm bir kapanışla kurulan static let için. static var ve static hesaplanan özellikler ayrıca ölçülmedi.

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

lazy özellik için kurduğumuz kural şu: ifade ilk okunuşta bir kez değerlendirilir. Bu kural bir sınıf örneği için doğrudur, çünkü sınıf örneği paylaşılan tek bir nesnedir. Aynı özelliği bir yapıya taşıyalım. Yapılar atamada kopyalanır ve kopya henüz okunmamış bir lazy özelliğin kendi kopyasını taşır. Sheet yapısında total tembel bir özelliktir; sheetA kopyalanır ve iki kopya da bir kez okunur.

// main.swift — yapidaki lazy ozellik kopyayla coaltir
struct Sheet {
    let probe: Probe
    var rows = [1, 2, 3]
    lazy var total: Int = probe.eval(rows.reduce(0, +))
}

let sheetProbe = Probe("yapi-lazy-kopya")
var sheetA = Sheet(probe: sheetProbe)
var sheetB = sheetA
_ = sheetProbe.use { sheetA.total }
_ = sheetProbe.use { sheetB.total }
sheetProbe.report()
yapi-lazy-kopya: 2 degerlendirme, 2 kullanim -> her-kullanimda

İki okuma, iki değerlendirme, an her kullanımda. İki kopya da aynı lazy özelliği ayrı ayrı kurdu, çünkü kopyalama anında özellik henüz kurulmamıştı ve her kopya kendi kurulmamış özelliğini aldı. Aynı değer iki kez hesaplandı.

Bu sonuç dersin kuralını daraltır. “Bir kez” cümlesi, bir örnek için geçerlidir. Yapıda kopya sayısı kadar örnek vardır ve henüz okunmamış bir lazy özellik her kopyada bağımsız olarak ilk okunuşta kurulur. Sınıfta kopya oluşmadığı için bu durum doğmaz.

Yapıdaki lazy özelliğin ikinci bir sınırı vardır ve o da ölçülemez, çünkü derlenmez. Okuma, özelliğe yazdığı için yapının değiştirilebilir olmasını gerektirir. Aşağıdaki parça let ile tanımlanan bir yapının lazy özelliğini okur ve derlenmez: derleyici onu reddeder. Hata iletisi, let ile tanımlanan bir yapı üzerinde değiştirici okumanın kullanılamayacağını bildirdi. Parça burada yalnızca anlatım için durur.

struct Sheet2 {
    var rows = [1, 2, 3]
    lazy var total: Int = rows.reduce(0, +)
}
let fixed = Sheet2()
print(fixed.total)   // derlenmez: let yapida lazy ozellik okunamaz

Bu, lazy özelliğin bir yan sonucudur: okuma bir yazma içerir. Okuma amaçlı kullanılacak yapı örneğinin var olması gerekir. Sınıfta aynı kısıt yoktur, çünkü let sınıf adı yalnız adı sabitler, nesnenin içeriğini değil.

TS20. Bu ölçüm iki kopya ve tek bir lazy özellik için. İç içe yapılar, lazy özelliğin başka bir lazy özelliğe bağlı olduğu durumlar ve reddedilen parçada yalnızca let yapı denendi; başka sabitleme biçimleri denenmedi.

Özet

  • Saklanan özellik değeri kurulurken, hesaplanan özellik her okunuşta, gözlemci her yazımda çalışır; tipin kendi başlatıcısındaki atama gözlemcileri tetiklemez.
  • Hesaplanan özellik set ile yazılabilir ve gerçek değeri başka bir saklanan özelliğe yansıtır.
  • lazy özellik ilk okunuşta bir kez değerlendirilir; varsayılan değer ise her örnekte oluşturma sırasında koşar.
  • static let ilk erişimde kurulur ve bir kez kurulur.
  • Yapıdaki henüz okunmamış lazy özellik kopyayla birlikte çoğalır; let yapıda okunamaz.
  • Bu dersin anı: ilk kullanımda. lazy özellik ilk okunuşta bir kez değerlendirilir.

Sonraki Adım

Bu derste özelliklerin değerini ne zaman kurduğunu ölçtük. Tipler yalnız veri taşımaz, aynı zamanda davranış sözü de verebilir. Bir sonraki ders protokolleri ölçer: bir tipin hangi özellikleri ve yöntemleri sağlaması gerektiğini bildiren sözleşmeleri, ve sözleşmeyi kullanan koddan çağrıldığında özelliğin hangi anda değerlendirildiğini.

İ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