Ders 16 / 21
Otomatik Referans Sayımı
Sınıf örneğinin son güçlü referansı bırakıldığı anda deinit'in çalıştığını dört bırakma yolunda ölçer ve ikinci bir referansın bu anı ertelediğini gösterir.
Yayın Güncelleme
Bu derste
- Son güçlü referans bırakılınca deinit'in çalıştığı noktayı print sırasından okur
- Kapsam sonu, nil ataması, diziden çıkarma ve döngü yinelemesinde deinit anını ölçer
- İkinci bir referansın deinit'i ilk bırakmadan sonraya ertelediğini gösterir
- Üst düzey bir let'in tuttuğu nesnenin deinit'inin çalışmadığını ölçer
İçindekiler
Önceki ders erişim düzeylerinin bir adı kimin görebildiğini değiştirdiğini, ama anı değiştirmediğini ölçtü: gizli bir lazy önbellek, private olsa da ilk kullanımda kuruluyordu. Bu ders görünürlükten çıkıp bir nesnenin ne zaman yok edildiğine geçer. Sınıf örnekleri otomatik referans sayımı (ARC) ile yönetilir: her örnek üzerindeki güçlü referansların sayısı tutulur ve bu sayı sıfıra düştüğü anda örnek yok edilir. Yok edilirken sınıfın deinit gövdesi koşar. Soru bu kursun sorusudur: deinit gövdesi ne zaman değerlendiriliyor?
Önceki müfredatlarda ömrün nerede bittiği ve belleği kimin sahiplendiği sorulmuştu. Bu ders ikisini de kategori yapmaz. Ömrün sınırı ya da sahibi ölçülmez; ölçülen, deinit gövdesinin hangi satırdan sonra ve kaç kez koştuğudur, yani an. Önce çekirdek verilir. Probe, bir ifadenin kaç kez değerlendirildiğini ve bunun kullanımdan önce mi sonra mı olduğunu kaydeder; tally dört anı sayar.
// 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)")
}
}
Ölçülecek sınıf Session’dır. deinit gövdesi önce bir satır basar, sonra sahibi olan Probe’a eval ile haber verir. Böylece hem print sırası hem de Probe sayacı aynı olayı gösterir.
// Session.swift — deinit'inde Probe'a yazan sinif
final class Session {
let name: String
let owner: Probe
init(_ name: String, owner: Probe) {
self.name = name
self.owner = owner
}
deinit {
print("deinit \(name)")
_ = owner.eval(name)
}
}
Dört Bırakma Yolu
Birinci ölçüm bir Session örneğini dört farklı yolla bırakır. İlk yol bir do kapsamının sonudur. İkincisi isteğe bağlı bir değişkene nil atamaktır. Üçüncüsü diziden removeLast() ile çıkarmaktır. Dördüncüsü bir for döngüsünün her yinelemesinde yeni örnek kurmaktır. Her yol en az iki kez çalışır, çünkü Probe’un kuralı hic dışındaki her gözlem için en az iki kullanım ister. Kural sayar, niyeti okumaz.
// main.swift — deinit hangi birakista kosar
let scopeEnd = Probe("kapsam-sonu")
let niled = Probe("nil-atamasi")
let removed = Probe("diziden-cikarma")
let looped = Probe("dongu-yinelemesi")
for round in 1...2 {
_ = scopeEnd.use {
do {
let temp = Session("kapsam-\(round)", owner: scopeEnd)
print("kullanildi \(temp.name)")
}
}
}
for round in 1...2 {
var held: Session? = Session("nil-\(round)", owner: niled)
_ = niled.use { held = nil }
print("sonra \(held == nil)")
}
var list = [Session("dizi-1", owner: removed), Session("dizi-2", owner: removed)]
_ = removed.use { list.removeLast() }
_ = removed.use { list.removeLast() }
print("dizide kalan \(list.count)")
for round in 1...3 {
let item = Session("dongu-\(round)", owner: looped)
_ = looped.use { item.name }
}
let probes = [scopeEnd, niled, removed, looped]
for probe in probes {
probe.report()
}
tally(probes)
kullanildi kapsam-1 deinit kapsam-1 kullanildi kapsam-2 deinit kapsam-2 deinit nil-1 sonra true deinit nil-2 sonra true deinit dizi-2 deinit dizi-1 dizide kalan 0 deinit dongu-1 deinit dongu-2 deinit dongu-3 kapsam-sonu: 2 degerlendirme, 2 kullanim -> her-kullanimda nil-atamasi: 2 degerlendirme, 2 kullanim -> her-kullanimda diziden-cikarma: 2 degerlendirme, 2 kullanim -> her-kullanimda dongu-yinelemesi: 3 degerlendirme, 3 kullanim -> her-kullanimda hemen: 0 ilk-kullanimda: 0 her-kullanimda: 4 hic: 0
Çıktıda önce print satırlarının sırasına bakılır. kullanildi kapsam-1 satırı deinit kapsam-1 satırından önce gelir: ölçülen koşumda örnek kapsamın sonunda yok edildi ve deinit satırı kapsam çıkışında göründü. nil atamasında deinit atamanın içinde koşar, ardından gelen sonra true satırı bunu doğrular. Dizide iki örnek vardır; removeLast() sondakini çıkarır, bu yüzden dizi-2 dizi-1’den önce yok edilir. Döngüde üç yineleme üç ayrı deinit üretir ve her deinit bir sonraki yinelemeden önce biter.
Dört Probe’un hepsi her-kullanimda der. İlk üçünde iki tur olduğu için iki değerlendirme, döngüde üç tur olduğu için üç değerlendirme sayılır. deinit gövdesi bir örnek her yok edildiğinde yeniden koşar ve her yok ediliş yeni bir örneğe aittir. Bu dersin anı bu yüzden her kullanımdadır. tally çıktısında hemen, ilk-kullanimda ve hic sıfırdır.
Fonksiyon Sınırı
Referansı yalnız değişkenler tutmaz; bir fonksiyona parametre olarak geçen örnek de çağrı süresince canlıdır. İkinci ölçüm iki durumu ayırır. Birinde fonksiyon örneği üretir ve çağıran tutar. Ötekinde üretilen örnek hiçbir değişkene bağlanmaz, yalnız bir ifadenin ortasında yaşar.
// main.swift — fonksiyon sinirinda referans
func inspect(_ session: Session) -> String {
session.name
}
func make(_ name: String, owner: Probe) -> Session {
Session(name, owner: owner)
}
let crossing = Probe("fonksiyon-siniri")
for round in 1...2 {
let made = make("uretilen-\(round)", owner: crossing)
_ = crossing.use { inspect(made) }
print("tur bitti \(round)")
}
for round in 1...2 {
_ = crossing.use { inspect(make("gecici-\(round)", owner: crossing)) }
print("gecici tur bitti \(round)")
}
crossing.report()
tur bitti 1 deinit uretilen-1 tur bitti 2 deinit uretilen-2 deinit gecici-1 gecici tur bitti 1 deinit gecici-2 gecici tur bitti 2 fonksiyon-siniri: 4 degerlendirme, 4 kullanim -> her-kullanimda
Birinci döngüde tur bitti 1 satırı deinit uretilen-1 satırından önce gelir. inspect çağrısı süresince örnek canlıdır ve çağrı örneğin ömrünü uzatmaz; örneğin sahibi hâlâ made sabitidir ve deinit yinelemenin sonunda, made kapsamdan çıkarken koşar. İkinci döngüde ise make ile üretilen örnek hiçbir ada bağlanmaz. Ölçülen koşumda örnek çağıran ifade biterken bırakıldı ve deinit gecici-1 satırı gecici tur bitti 1 satırından önce görünür. İki döngü birlikte fonksiyon-siniri için dört değerlendirme ve dört kullanım kaydeder. Yani bir fonksiyona örnek geçirmek ömrü uzatmaz; ömrü uzatan, örneği bir adda ya da bir özellikte saklamaktır.
Zincirleme Bırakma
Bir örnek başka bir örneği özelliğinde tutabilir. Bu durumda dıştaki örneğin yok edilmesi içtekinin son referansını da bırakır. Üçüncü ölçüm iki zinciri birlikte kurar: Holder bir Session tutar, Car ise Vehicle sınıfından türer ve iki sınıfın da deinit gövdesi vardır. Soru, hangi deinit gövdesinin önce koştuğudur.
// main.swift — ozellikteki ornek ve alt sinif zinciri
final class Holder {
let name: String
let child: Session
init(_ name: String, owner: Probe) {
self.name = name
child = Session("cocuk-\(name)", owner: owner)
}
deinit {
print("deinit \(name)")
}
}
class Vehicle {
let owner: Probe
init(owner: Probe) {
self.owner = owner
}
deinit {
print("deinit ust-sinif")
_ = owner.eval("ust")
}
}
final class Car: Vehicle {
deinit {
print("deinit alt-sinif")
_ = owner.eval("alt")
}
}
let nested = Probe("ozellikteki-ornek")
let inherited = Probe("ust-sinif-zinciri")
for round in 1...2 {
var holder: Holder? = Holder("sahip-\(round)", owner: nested)
_ = nested.use { holder = nil }
}
for _ in 1...2 {
var car: Car? = Car(owner: inherited)
_ = inherited.use { car = nil }
}
nested.report()
inherited.report()
deinit sahip-1 deinit cocuk-sahip-1 deinit sahip-2 deinit cocuk-sahip-2 deinit alt-sinif deinit ust-sinif deinit alt-sinif deinit ust-sinif ozellikteki-ornek: 2 degerlendirme, 2 kullanim -> her-kullanimda ust-sinif-zinciri: 4 degerlendirme, 2 kullanim -> her-kullanimda
Çıktıda önce deinit sahip-1, ardından deinit cocuk-sahip-1 görünür: dıştaki örneğin deinit gövdesi önce koşar, gövde biter bitmez özellikleri bırakılır ve içteki örneğin deinit’i bunu izler. Bu yüzden dıştaki deinit gövdesi içtekine hâlâ erişebilir. Alt sınıf zincirinde önce alt sınıfın, sonra üst sınıfın deinit gövdesi koşar. ozellikteki-ornek iki turda iki değerlendirme sayar, çünkü yalnız içteki Session onu çağırır. ust-sinif-zinciri ise tur başına iki eval gördüğü için iki turda dört değerlendirme sayar. İkisi de her-kullanimda der: değerlendirme sayısı kullanım sayısından fazla ya da ona eşit olabilir, kural yalnız bir kereden fazla koşup koşmadığına bakar. deinit açıkça çağrılamaz ve parametre almaz; yalnız sayaç sıfıra düştüğünde, bir kez, çalışma zamanı tarafından koşturulur.
Üzerine Yazma Eskiyi Bırakır
nil ataması bırakmanın tek biçimi değildir. Bir var özelliğe yeni bir örnek atamak, özelliğin tuttuğu eski örneğin referansını da bırakır. Bu ölçüm bir Slot sınıfının session özelliğine ikinci bir Session atar ve atamanın iki yanında evaluations değerini yazdırır. Yeni örnek ayrı bir Probe’a bağlıdır; böylece sayılan deinit yalnız eski örneğe aittir.
// main.swift — ozelligin ustune yazma
final class Slot {
var session: Session?
init(_ session: Session?) {
self.session = session
}
}
let overwritten = Probe("ustune-yazma")
let spare = Probe("yedek")
for round in 1...2 {
let slot = Slot(Session("eski-\(round)", owner: overwritten))
print("yazmadan once \(overwritten.evaluations)")
_ = overwritten.use { slot.session = Session("yeni-\(round)", owner: spare) }
print("yazmadan sonra \(overwritten.evaluations)")
}
overwritten.report()
yazmadan once 0 deinit eski-1 yazmadan sonra 1 deinit yeni-1 yazmadan once 1 deinit eski-2 yazmadan sonra 2 deinit yeni-2 ustune-yazma: 2 degerlendirme, 2 kullanim -> her-kullanimda
Her turda yazmadan once 0 ve yazmadan sonra 1 satırları arasında deinit eski-1 görünür: atama satırı yeni değeri yerleştirirken eski değerin son güçlü referansını bırakır ve eski örnek, atama bitmeden yok edilir. deinit yeni-1 ise tur sonunda gelir, çünkü slot o noktada kapsamdan çıkar ve içindeki örneği de bırakır. Yani aynı özellik tur boyunca iki ayrı örneği sırayla tutmuş, iki ayrı deinit üretmiştir. ustune-yazma iki turda iki değerlendirme sayar ve her-kullanimda der. Kural değişmemiştir: deinit bir bırakma satırına bağlı değildir, sayacın sıfıra düşmesine bağlıdır. Üzerine yazma bu sayaçtan bir azaltır; nil ataması da, kapsam sonu da aynı işi yapar.
Toplu Boşaltma
Bir dizinin tuttuğu örnekler tek tek değil, dizi boşaltılırken bırakılır. Bu ölçüm iki ayrı dizi kurar ve her birini removeAll() ile boşaltır. Soru, tek bir çağrının kaç deinit gövdesi koşturduğudur.
// main.swift — removeAll ile toplu birakma
let cleared = Probe("toplu-bosaltma")
for round in 1...2 {
var batch = [
Session("toplu-\(round)-a", owner: cleared),
Session("toplu-\(round)-b", owner: cleared)
]
print("bosaltmadan once \(cleared.evaluations)")
_ = cleared.use { batch.removeAll() }
print("bosaltmadan sonra \(cleared.evaluations), dizide \(batch.count)")
}
cleared.report()
bosaltmadan once 0 deinit toplu-1-a deinit toplu-1-b bosaltmadan sonra 2, dizide 0 bosaltmadan once 2 deinit toplu-2-a deinit toplu-2-b bosaltmadan sonra 4, dizide 0 toplu-bosaltma: 4 degerlendirme, 2 kullanim -> her-kullanimda
removeAll() çağrısı tek satırdır, ama içinde iki örnek bulunduğundan iki deinit koşar: bosaltmadan once 0 ile bosaltmadan sonra 2 arasında deinit toplu-1-a ve deinit toplu-1-b görünür. İkinci turda sayaç 2’den 4’e çıkar. Burada Probe iki kullanıma karşılık dört değerlendirme sayar ve yine her-kullanimda der; bir çağrının birden çok deinit koşturabilmesi her-kullanimda kuralıyla çelişmez, çünkü kural değerlendirme sayısına değil, bir kereden fazla koşup koşmadığına bakar. Dizinin bıraktığı her örnek kendi sayacına sahiptir. Biri başka bir değişkende de tutuluyor olsaydı, removeAll() yalnız dizinin payını bırakır ve o örneğin deinit’i sayaç sıfıra düşene kadar beklerdi; sınırlayıcı ölçüm bu durumu gösterir.
Sınırlayıcı Ölçüm
Yukarıdaki kural, deinit’in her bırakmada koştuğu izlenimini verebilir. Son ölçüm bunun yanlış olduğunu gösterir. Aynı örneği iki değişken tutar: first ve second. Önce first bırakılır, sonra second. shared.evaluations her adımdan sonra okunur.
// main.swift — ikinci referans ve ust duzey let
let shared = Probe("ikinci-referans")
let outlives = Probe("ust-duzey-let")
for round in 1...2 {
var first: Session? = Session("ortak-\(round)", owner: shared)
var second = first
_ = shared.use { first = nil }
print("birinci birakildi: \(shared.evaluations) deinit, ikinci \(second == nil ? "yok" : "var")")
_ = shared.use { second = nil }
print("ikinci birakildi: \(shared.evaluations) deinit")
}
let pinned = Session("ust-duzey", owner: outlives)
_ = outlives.use { pinned.name }
_ = outlives.use { pinned.name }
shared.report()
outlives.report()
birinci birakildi: 0 deinit, ikinci var deinit ortak-1 ikinci birakildi: 1 deinit birinci birakildi: 1 deinit, ikinci var deinit ortak-2 ikinci birakildi: 2 deinit ikinci-referans: 2 degerlendirme, 4 kullanim -> her-kullanimda ust-duzey-let: 0 degerlendirme, 2 kullanim -> hic
İlk bırakmadan sonra deinit sayısı sıfırdır ve second hâlâ örneği tutar. deinit ortak-1 satırı yalnız ikinci bırakmada görünür. İkinci turda aynı sıra yinelenir. Sayacın sıfıra düşmesi deinit’in koşma koşuludur; bırakma satırının kendisi koşul değildir. ikinci-referans Probe’u iki değerlendirme ve dört kullanım kaydeder ve her-kullanimda der, ama bunu tek başına söylemez: hangi bırakmanın deinit’i tetiklediği yalnız print sırasından ve araya konan evaluations okumalarından anlaşılır.
Son iki satır ikinci sınırı gösterir. Üst düzey let ile tutulan pinned iki kez kullanılır, ama ust-duzey-let Probe’u sıfır değerlendirme kaydeder ve hic der. Üst düzey bir let süreç sonuna kadar tutulur ve bu ölçümde süreç sonunda deinit koşmaz. deinit’in koşmaması bir hata değildir; referans sayısı hiç sıfıra düşmemiştir. Böyle bir örneğin deinit gövdesine güvenerek bir kaynağı kapatmak yanlış olur.
BL1. Her bırakma yolu için yalnızca bir örnek ölçüldü; kuralın başka bırakma yollarında da geçtiği bu derste gösterilmedi. Dört yolun seçilmesi tasarım tercihidir.
Özet
- Sınıf örnekleri otomatik referans sayımıyla (ARC) yönetilir; son güçlü referans bırakıldığı anda
deinitkoşar. - Kapsam sonu,
nilataması, diziden çıkarma ve döngü yinelemesi aynı kuralı izler:deinitbırakma noktasında koşar. - İkinci bir güçlü referans
deinit’i erteler; sayaç sıfıra düşmedendeinitkoşmaz. - Üst düzey bir
let’in tuttuğu örnek süreç sonuna kadar bırakılmaz ve bu ölçümdedeinitkoşmaz (hic). - Bu dersin anı: her kullanımda. Her yeni örneğin yok edilişi
deinitgövdesini yeniden koşturur.
Sonraki Adım
Bu derste sayaç sıfıra düşmek zorunda kaldı ve düştü. İki sınıf birbirini güçlü tutarsa sayaç hiçbir zaman sıfıra düşmez ve iki deinit de koşmaz. Sonraki ders, weak ve unowned ile bu döngüyü kırmayı ve deinit gövdesinin neden 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.