İçeriğe geç
academia.sh

Ders 17 / 21

Güçlü, Zayıf ve Sahipsiz Referanslar

İki sınıfın birbirini güçlü tutunca deinit'in hiç çalışmadığını, weak ve unowned ile döngünün kırıldığını ve unowned referansın nesne gittikten sonra okunamayacağını ölçer.

Yayın Güncelleme

Bu derste

  • İki sınıfın birbirini güçlü tutmasıyla oluşan tutma döngüsünü deinit çıktısından tanır
  • Bir referansı weak yaparak döngüyü kırar ve weak referansın nesne gidince nil olduğunu gözler
  • Ömrü daha uzun olan tarafa unowned referans bağlar
  • Weak ile unowned arasındaki farkı nesne gittikten sonraki okumada ayırt eder
İçindekiler

Önceki derste referans sayısı sıfıra düştü ve deinit koştu. Sayının sıfıra düşmesi bir koşuldu, garanti değil. İki sınıf örneği birbirini güçlü referans ile tutarsa her biri ötekinin sayacını bir üstte tutar ve dışarıdaki tüm adlar bırakılsa bile ikisi de yaşar. Bu duruma tutma döngüsü denir. Bu ders döngüyü kurar, iki yoldan kırar ve her yolda deinit gövdesinin ne zaman koştuğunu ölçer.

İki kırma aracı vardır. Zayıf referans (weak) örneği tutmaz: sayacı artırmaz ve gösterdiği örnek yok edilince kendiliğinden nil olur, bu yüzden her zaman isteğe bağlı bir var olur. Sahipsiz referans (unowned) da sayacı artırmaz, ama nil olmaz ve isteğe bağlı olmak zorunda değildir; bunun karşılığında gösterdiği örneğin kendisinden uzun yaşayacağı sözünü ister. Probe önceki dersteki gibi verilir.

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

Tutma Döngüsü

Ölçülen iki sınıf Account ve Profile’dır. Her ikisinin de deinit gövdesi bir satır basar ve ortak Probe’a eval ile haber verir. Account bir Profile tutar, Profile da bir Account tutar; ikisi de varsayılan, yani güçlü referanstır. Her turda iki örnek kurulur, birbirine bağlanır ve ardından ikisine de nil atanır. Dışarıdaki adlar böylece bırakılmış olur.

// main.swift — iki sinif birbirini guclu tutar
final class Account {
    let name: String
    let owner: Probe
    var profile: Profile?

    init(_ name: String, owner: Probe) {
        self.name = name
        self.owner = owner
    }

    deinit {
        print("deinit hesap \(name)")
        _ = owner.eval(name)
    }
}

final class Profile {
    let title: String
    let owner: Probe
    var account: Account?

    init(_ title: String, owner: Probe) {
        self.title = title
        self.owner = owner
    }

    deinit {
        print("deinit profil \(title)")
        _ = owner.eval(title)
    }
}

let cyclic = Probe("tutma-dongusu")

for round in 1...2 {
    var account: Account? = Account("hesap-\(round)", owner: cyclic)
    var profile: Profile? = Profile("profil-\(round)", owner: cyclic)
    account?.profile = profile
    profile?.account = account
    _ = cyclic.use {
        account = nil
        profile = nil
    }
    print("birakildi \(round): \(cyclic.evaluations) deinit")
}

cyclic.report()
birakildi 1: 0 deinit
birakildi 2: 0 deinit
tutma-dongusu: 0 degerlendirme, 2 kullanim -> hic

İki turun ikisinde de bırakmadan sonra deinit sayısı sıfırdır ve deinit satırı hiç görünmez. Her örnek ötekinin özelliği tarafından tutulduğu için iki sayaç da en az bire eşit kalır. tutma-dongusu iki kullanım ve sıfır değerlendirme kaydeder; kural hic der. Bu dersin anı bu yüzden hiçtir: deinit gövdesi hiçbir anda değerlendirilmez. Üstelik hiçbir şey hata vermez. Döngüdeki iki örnek program sonuna kadar bellekte kalır.

Zayıf Referansla Kırmak

Döngüyü kırmak için iki taraftan yalnız birinin referansı zayıf yapılır. İkinci ölçüm Profile.account özelliğini weak var olarak tanımlar, başka hiçbir şeyi değiştirmez. Bu kez örnekler iki adımda bırakılır: önce account, sonra profile. Her adımdan sonra profile?.account okunur ve deinit sayısı basılır.

// main.swift — bir taraf zayif tutar
final class Account {
    let name: String
    let owner: Probe
    var profile: Profile?

    init(_ name: String, owner: Probe) {
        self.name = name
        self.owner = owner
    }

    deinit {
        print("deinit hesap \(name)")
        _ = owner.eval(name)
    }
}

final class Profile {
    let title: String
    let owner: Probe
    weak var account: Account?

    init(_ title: String, owner: Probe) {
        self.title = title
        self.owner = owner
    }

    deinit {
        print("deinit profil \(title)")
        _ = owner.eval(title)
    }
}

let broken = Probe("zayif-referans")

for round in 1...2 {
    var account: Account? = Account("hesap-\(round)", owner: broken)
    var profile: Profile? = Profile("profil-\(round)", owner: broken)
    account?.profile = profile
    profile?.account = account
    print("baglandi: \(profile?.account == nil ? "nil" : "dolu")")
    _ = broken.use { account = nil }
    print("hesap birakildi: \(profile?.account == nil ? "nil" : "dolu"), \(broken.evaluations) deinit")
    _ = broken.use { profile = nil }
    print("profil birakildi: \(broken.evaluations) deinit")
}

broken.report()
baglandi: dolu
deinit hesap hesap-1
hesap birakildi: nil, 1 deinit
deinit profil profil-1
profil birakildi: 2 deinit
baglandi: dolu
deinit hesap hesap-2
hesap birakildi: nil, 3 deinit
deinit profil profil-2
profil birakildi: 4 deinit
zayif-referans: 4 degerlendirme, 4 kullanim -> her-kullanimda

Bağlandıktan sonra profile?.account doludur. account adı bırakıldığında Account örneğinin güçlü referansı kalmaz: profile.account zayıf olduğu için sayacı artırmamıştı. deinit hesap hesap-1 satırı hemen çıkar ve ardından gelen okuma nil verir; zayıf referans, gösterdiği örnek yok edildiği anda kendiliğinden nil olur. Profile örneği ise profile adıyla hâlâ tutulduğundan yaşar, deinit profil satırı ancak o ad bırakılınca görünür. İki turda dört deinit koştu ve zayif-referans her-kullanimda der.

Sahipsiz Referansla Kırmak

Zayıf referans örneğin yok olabileceğini söyler. Bir ilişki bu yönde değilse, yani Card bir Customer olmadan hiçbir zaman var olmayacaksa, referans unowned let olarak yazılır. Üçüncü ölçüm bu ilişkiyi kurar: Customer isteğe bağlı bir card tutar, Card ise unowned let customer taşır. Müşteri kartı tutar, kart müşteriyi tutmaz. Ölçüm yalnız müşteri adını bırakır.

// main.swift — sahipsiz referans daha uzun yasayana baglanir
final class Customer {
    let name: String
    let owner: Probe
    var card: Card?

    init(_ name: String, owner: Probe) {
        self.name = name
        self.owner = owner
    }

    deinit {
        print("deinit musteri \(name)")
        _ = owner.eval(name)
    }
}

final class Card {
    let number: String
    let owner: Probe
    unowned let customer: Customer

    init(_ number: String, customer: Customer, owner: Probe) {
        self.number = number
        self.customer = customer
        self.owner = owner
    }

    deinit {
        print("deinit kart \(number)")
        _ = owner.eval(number)
    }
}

let bound = Probe("sahipsiz-referans")

for round in 1...2 {
    var customer: Customer? = Customer("musteri-\(round)", owner: bound)
    if let current = customer {
        current.card = Card("kart-\(round)", customer: current, owner: bound)
        print("kart sahibi: \(current.card?.customer.name ?? "yok")")
    }
    _ = bound.use { customer = nil }
    print("birakildi \(round): \(bound.evaluations) deinit")
}

bound.report()
kart sahibi: musteri-1
deinit musteri musteri-1
deinit kart kart-1
birakildi 1: 2 deinit
kart sahibi: musteri-2
deinit musteri musteri-2
deinit kart kart-2
birakildi 2: 4 deinit
sahipsiz-referans: 4 degerlendirme, 2 kullanim -> her-kullanimda

Her turda deinit musteri satırı deinit kart satırından önce gelir. Müşteri bırakılınca sayacı sıfıra düşer, deinit gövdesi koşar, ardından özellikleri bırakılır ve kartın tek güçlü referansı da gider. Bu zincirde döngüyü kıran, kartın müşteriyi güçlü tutmamasıdır; unowned bunu isteğe bağlı olmadan ifade eder. sahipsiz-referans iki kullanım ve dört değerlendirme kaydeder, iki deinit her turda koşar ve kural her-kullanimda der.

Temsilci Deseni

Zayıf referansın en yaygın yeri temsilci desenidir: bir nesne işini bitirince haber vereceği ikinci bir nesneyi saklar, haber alan ise işi yapan nesneyi zaten tutar. İki yönün de güçlü olması tutma döngüsü kurardı. Bu ölçüm Worker sınıfının weak var delegate özelliğini kullanır; Reporter ise Worker’ı güçlü tutar. Tur sonunda yalnız reporter adı bırakılır ve worker adı elde kalır.

// main.swift — temsilci zayif, calisan guclu
final class Reporter {
    let name: String
    let owner: Probe
    let worker: Worker

    init(_ name: String, owner: Probe) {
        self.name = name
        self.owner = owner
        worker = Worker()
        worker.delegate = self
    }

    deinit {
        print("deinit raporlayici \(name)")
        _ = owner.eval(name)
    }
}

final class Worker {
    weak var delegate: Reporter?
}

let handedOver = Probe("temsilci")
for round in 1...2 {
    var reporter: Reporter? = Reporter("raporlayici-\(round)", owner: handedOver)
    let worker = reporter!.worker
    print("temsilci \(worker.delegate?.name ?? "nil")")
    _ = handedOver.use { reporter = nil }
    print("birakildi: temsilci \(worker.delegate?.name ?? "nil"), \(handedOver.evaluations) deinit")
}

handedOver.report()
temsilci raporlayici-1
deinit raporlayici raporlayici-1
birakildi: temsilci nil, 1 deinit
temsilci raporlayici-2
deinit raporlayici raporlayici-2
birakildi: temsilci nil, 2 deinit
temsilci: 2 degerlendirme, 2 kullanim -> her-kullanimda

Bağlandıktan sonra worker.delegate raporlayıcının adını verir. reporter adı bırakıldığında Reporter örneğinin güçlü referansı kalmaz: Worker onu zayıf tuttuğu için sayacı artırmamıştı. deinit raporlayici raporlayici-1 satırı hemen çıkar ve ardından gelen okuma nil verir. worker ise worker adıyla elde tutulduğundan yaşamaya devam eder; Reporter onu özelliğinde tuttuğu halde kendisi gittiği için Worker’ın payı yalnız dışarıdaki addır. Desenin işleyişi bu asimetriye dayanır: haber alan, haber verenden uzun yaşamak zorunda değildir ve haber veren, haber alanın gittiğini nil okuyarak öğrenir. Bu yüzden temsilci özelliği her zaman isteğe bağlı bir değer olarak yazılır ve her kullanımdan önce kontrol edilir. İki turda iki deinit koşar ve temsilci her-kullanimda der.

Zayıf ve Sahipsiz Okuma

İki referans biçimi, gösterdikleri örnek yaşadığı sürece aynı değeri verir. Ayrım örnek gittikten sonra başlar. Bu ölçüm bir Watcher kurar: biri weak var, öteki unowned let iki özellik aynı Target örneğini gösterir. Örnek yaşarken ikisi de okunur, sonra örnek bırakılır ve yalnız zayıf özellik okunur. Sahipsiz özellik bu noktadan sonra okunmaz; okuma programı durdururdu.

// main.swift — ayni hedefe zayif ve sahipsiz bakis
final class Target {
    let name: String
    let owner: Probe

    init(_ name: String, owner: Probe) {
        self.name = name
        self.owner = owner
    }

    deinit {
        print("deinit hedef \(name)")
        _ = owner.eval(name)
    }
}

final class Watcher {
    weak var soft: Target?
    unowned let strict: Target

    init(_ target: Target) {
        soft = target
        strict = target
    }
}

let looked = Probe("zayif-ve-sahipsiz")
for round in 1...2 {
    var target: Target? = Target("hedef-\(round)", owner: looked)
    let watcher = Watcher(target!)
    print("yasarken: zayif \(watcher.soft?.name ?? "nil"), sahipsiz \(watcher.strict.name)")
    _ = looked.use { target = nil }
    print("gittikten sonra: zayif \(watcher.soft?.name ?? "nil")")
}

looked.report()
yasarken: zayif hedef-1, sahipsiz hedef-1
deinit hedef hedef-1
gittikten sonra: zayif nil
yasarken: zayif hedef-2, sahipsiz hedef-2
deinit hedef hedef-2
gittikten sonra: zayif nil
zayif-ve-sahipsiz: 2 degerlendirme, 2 kullanim -> her-kullanimda

Örnek yaşarken iki okuma da hedef-1 verir; iki biçim bu anda birbirinden ayırt edilemez. deinit hedef hedef-1 satırından sonra zayıf okuma nil verir: örnek yok edildikten sonra zayıf referans nil okunur. Sahipsiz okuma ise aynı noktada ölçülmez: strict.name satırı gitmiş bir örneği okuyacaktı ve çalışma zamanı programı durdururdu. Bu nedenle iki biçim arasındaki seçim bir okuma biçimi seçimi değil, ne olması gerektiği sözüdür. Hedefin watcher’dan önce gitmesi mümkünse zayıf, imkânsızsa sahipsiz yazılır. zayif-ve-sahipsiz iki turda iki değerlendirme sayar ve her-kullanimda der; zayıf okumanın sonucu ise her okuma anında yeniden belirlenir. Önceki okumanın sonucu sonraki okumaya taşınmaz.

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

Zayıf ve sahipsiz referans yalnız gösterdiklerini tutmamakta ortaktır; gösterdikleri örnek gittikten sonra okunduklarında ayrılırlar. Zayıf referans nil verir ve bunu ikinci ölçüm yukarıda gösterdi. Sahipsiz referansın böyle bir güvencesi yoktur: gitmiş bir örneğe unowned ile erişmek çalışma zamanında programı durdurur. Bu yüzden bu okuma ölçülmez. Aşağıdaki parça yalnız kaynaktır ve çalıştırılmaz. Çıktı iddiası da yoktur.

// Kaynak parca: calistirilmaz
var customer = Customer("musteri-3", owner: bound)
let card = Card("kart-3", customer: customer, owner: bound)
customer = Customer("musteri-4", owner: bound)
print(card.customer.name)

Üçüncü satır musteri-3’ün son güçlü referansını bırakır; Card onu tutmadığı için örnek yok edilir. Dördüncü satır yok edilmiş bir örneği okur. Aynı yapı weak ile yazılsaydı okuma nil verirdi ve program devam ederdi. Seçim bu yüzden bir ömür sözüdür: unowned yalnız referansı taşıyan örnekten daha uzun yaşadığı kesin olan bir örneğe bağlanır; emin olunmayan her yerde weak kullanılır. Derleyici bu sözü denetlemez, bu yüzden bu bir sınırdır, bir an değil.

BL2. Her bağlantı biçimi için tek bir sınıf çifti ölçüldü; üç çiftin hepsi iki sınıflı küçük zincirlerdir. Daha uzun döngülerde (üç ve daha çok sınıf) aynı kuralın geçtiği bu derste gösterilmedi. Çiftlerin seçimi tasarım tercihidir.

Özet

  • İki sınıf birbirini güçlü tutarsa referans sayıları sıfıra düşmez ve iki deinit de koşmaz; bu bir tutma döngüsüdür.
  • Bir tarafı weak var yapmak döngüyü kırar; zayıf referans gösterdiği örnek yok edilince nil olur.
  • unowned let ömrü daha uzun olan tarafa bağlanır ve nil olmaz; yok edilmiş örneği okumak programı durdurur, bu yüzden ölçülmez.
  • Bu dersin anı: hiç. Tutma döngüsündeki deinit gövdesi hiçbir anda değerlendirilmez; döngü kırıldığında her turda koşar.

Sonraki Adım

Bu derste döngü iki sınıf özelliği arasında kuruldu. Bir sınıfın kendi özelliğinde sakladığı kapanış da self’i güçlü yakalayarak aynı döngüyü kurabilir ve bu, tutma döngülerinin en sık rastlanan biçimidir. Sonraki ders kapanışlarda yakalamayı ve [weak self] yakalama listesinin ne zaman değerlendirildiğ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