Ders 17 / 19
Nesne Kalıcılık Çerçeveleri
Nesneleri bir bağlamda oluşturup kaydetmenin yinelemeye duyarlı, benzersizlik kısıtlı kaydın ise sıraya duyarlı olduğunun ve kısıtın sınıfı nasıl değiştirdiğinin ölçümü.
Yayın Güncelleme
Bu derste
- Nesne kalıcılık çerçevesinin sorgular yerine nesnelerle çalıştığını ayırır
- Bağlamda yeni nesne oluşturup kaydetmenin yinelemeye duyarlı olduğunu ölçer
- Benzersizlik kısıtlı kaydın aynı değerde sağlam, farklı değerde sıraya duyarlı olduğunu doğrular
- Kısıt kaldırıldığında aynı kaydın sınıfının değiştiğini gösterir
İçindekiler
Önceki derste veritabanına satır ve birincil anahtar düzeyinde yaklaştık. Çoğu uygulama satırlarla değil, kendi tiplerinden nesnelerle düşünür: bir not, bir sipariş, bir kişi. Nesne kalıcılık çerçevesi bu nesneleri bir bağlam içinde izler, değişiklikleri biriktirir ve kaydedildiğinde alttaki depoya yazar. Satırın karşılığı nesnedir; birincil anahtarın karşılığı ise çoğu zaman bir benzersizlik kısıtıdır. Sorumuz değişmez: bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi?
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
let label: String
let once: State
let twice: State
let forward: State
let backward: State
var sensitivity: String {
switch (once != twice, forward != backward) {
case (false, false): return "saglam"
case (true, false): return "yinelemeye-duyarli"
case (false, true): return "siraya-duyarli"
case (true, true): return "cift-duyarli"
}
}
func report() -> String {
print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
return sensitivity
}
}
func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}
func countSensitivities(_ results: [String]) {
for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
print("\(name): \(results.filter { $0 == name }.count)")
}
}
bir işlemin başlangıç durumuna bir kez, iki iki kez uygulanmasıdır; ab önce a sonra b, ba ise tersi sıradır. Model bir bağlamın saf Swift karşılığıdır. Çerçevenin kendisi değildir; yalnız bir nesneyi oluşturup kaydetmenin sonucunu kurar.
Bağlam, Nesne ve Kayıt
Bir bağlam, bellekte yaşayan nesneler kümesinin yöneticisidir. Uygulama yeni bir nesne oluşturur, bağlama ekler ve kaydeder. Kaydedilene kadar değişiklik yalnız bellektedir. İki yazım dili vardır: eski çerçeve bir kalıcı kap ve bir yönetilen nesne bağlamı kullanır; yeni çerçeve @Model ile işaretlenmiş sınıflar ve bir model bağlamı kullanır.
import SwiftData
@Model
final class Note {
@Attribute(.unique) var id: String
var body: String
init(id: String, body: String) {
self.id = id
self.body = body
}
}
let container = try ModelContainer(for: Note.self)
let context = ModelContext(container)
context.insert(Note(id: "k1", body: "a1"))
try context.save()
Bu parça KAYNAK’tır; bir model kabı ve disk gerektirdiği için burada koşmaz. @Attribute(.unique) bir niteliği benzersiz yapar: aynı id ile gelen ikinci nesne yeni bir kayıt açmaz, mevcut kaydı günceller. Nitelik kısıtsız bırakılırsa her ekleme yeni bir kayıt olur. Eski çerçevede aynı amaç benzersizlik kısıtıyla ve bir birleştirme politikasıyla sağlanır. Bu dersin ölçümü iki durumu karşılaştırır.
Bağlamın Modeli
Model, kayıtların bir listesini tutar. Her kayıt bir anahtar ve bir değerdir. Kısıt açıksa aynı anahtarla gelen kayıt mevcut kaydın yerine geçer; kapalıysa yeni bir kayıt olarak eklenir. Anahtarsız oluşturulan nesneye bağlam kendi anahtarını verir.
// ContextModel.swift — bir baglamin saf Swift modeli
struct ContextBox: Equatable, CustomStringConvertible {
struct Entry {
let key: String
var value: String
}
private(set) var entries: [Entry] = []
private(set) var nextId = 1
let enforcesUnique: Bool
init(enforcesUnique: Bool = true) {
self.enforcesUnique = enforcesUnique
}
var description: String {
entries.isEmpty ? "-" : entries.map { "\($0.key)=\($0.value)" }.joined(separator: ",")
}
static func == (lhs: ContextBox, rhs: ContextBox) -> Bool {
lhs.description == rhs.description
}
mutating func create(_ value: String) {
entries.append(Entry(key: "obj\(nextId)", value: value))
nextId += 1
}
mutating func save(_ value: String, key: String) {
if enforcesUnique, let index = entries.firstIndex(where: { $0.key == key }) {
entries[index].value = value
} else {
entries.append(Entry(key: key, value: value))
}
}
}
func creating(_ value: String) -> (ContextBox) -> ContextBox {
{ context in
var next = context
next.create(value)
return next
}
}
func saving(_ value: String, key: String) -> (ContextBox) -> ContextBox {
{ context in
var next = context
next.save(value, key: key)
return next
}
}
KL12. Model, aynı benzersiz anahtar geldiğinde son yazanın kazandığını varsayar: mevcut kaydın değeri yenisiyle değişir. Çerçevenin gerçek birleştirme politikası ve bunun ayarları ölçülmedi.
KL13. Model, her işlemi eklemek ve hemen kaydetmek olarak kurar. Kaydedilmemiş değişiklikler, geri alma ve birden çok bağlam kurulmadı.
Dört İşlemin Ölçümü
Dört işlem kurulur. Birincisi aynı değerle yeni bir nesne oluşturmaktır. İkincisi iki farklı değerle iki nesne oluşturmaktır. Üçüncüsü benzersiz bir anahtara aynı değeri iki kez kaydetmektir. Dördüncüsü aynı anahtara iki farklı değer kaydetmektir.
// main.swift — nesne olusturma ve benzersiz kayit
let createSame = replay("olustur-ayni-deger", from: ContextBox(), creating("a1"), then: creating("a1")).report()
let createTwo = replay("olustur-iki-deger", from: ContextBox(), creating("a1"), then: creating("b2")).report()
let uniqueSame = replay("kisitli-ayni-deger", from: ContextBox(), saving("a1", key: "k1"), then: saving("a1", key: "k1")).report()
let uniqueTwo = replay("kisitli-iki-deger", from: ContextBox(), saving("a1", key: "k1"), then: saving("b2", key: "k1")).report()
countSensitivities([createSame, createTwo, uniqueSame, uniqueTwo])
olustur-ayni-deger | bir=obj1=a1 iki=obj1=a1,obj2=a1 ab=obj1=a1,obj2=a1 ba=obj1=a1,obj2=a1 -> yinelemeye-duyarli olustur-iki-deger | bir=obj1=a1 iki=obj1=a1,obj2=a1 ab=obj1=a1,obj2=b2 ba=obj1=b2,obj2=a1 -> cift-duyarli kisitli-ayni-deger | bir=k1=a1 iki=k1=a1 ab=k1=a1 ba=k1=a1 -> saglam kisitli-iki-deger | bir=k1=a1 iki=k1=a1 ab=k1=b2 ba=k1=a1 -> siraya-duyarli saglam: 1 yinelemeye-duyarli: 1 siraya-duyarli: 1 cift-duyarli: 1
Aynı değerle yeni nesne oluşturmak yinelendiğinde iki nesne bırakır: obj1=a1 ve obj1=a1,obj2=a1. Sıra anlamsızdır. Sınıf yinelemeye duyarlıdır. Bir ekranın yeniden görünmesiyle ya da bir yeniden denemeyle ikinci kez koşan oluşturma kodu, kullanıcıya çift kayıt olarak yansır.
İki farklı değerle oluşturma hem yinelemeyi hem sırayı ayrıştırır: kimlikler değerlere farklı dağılır. Sınıf çift duyarlıdır.
Benzersiz anahtara aynı değeri iki kez kaydetmek hiçbir şeyi değiştirmez. Kısıt, yinelemeyi sağlam kılar. Sınıf sağlamdır.
Aynı anahtara iki farklı değer kaydedilince ise sonucu sıra belirler: ab b2, ba a1. Kısıt çift kaydı önler, ama çakışmada hangi değerin kazanacağı sıraya kalır. Sınıf sıraya duyarlıdır.
Sayım her sınıftan birer tanedir.
Şema Göçü
Bir uygulamanın nesne tipi sürümler arasında değişir: bir nitelik eklenir, bir ad değişir, iki tip birleşir. Kullanıcının aygıtında ise eski sürümün yazdığı veri durur. Şema göçü, eski şemayla yazılmış veriyi yeni şemaya taşıyan adımdır. Basit değişiklikler, örneğin bir niteliğin eklenmesi, çerçeve tarafından kendiliğinden yapılabilir; bir adın değişmesi ya da iki alanın birleşmesi gibi değişiklikler açık bir eşleme gerektirir.
Şema göçü bu dersin modelinde kurulmadı. Çünkü sonucu depodaki eski veriye ve sürüm sırasına bağlıdır; bir bağlamın bellekteki hâlinden okunamaz. Burada KAYNAK ve düzyazı olarak kalır. Pratik kural basittir: şemayı değiştiren her sürüm, eski sürümün yazdığı bir veriyle denenmeden yayımlanmaz.
Sık Yapılan Hatalar
- Oluşturma kodunu bir ekranın görünme olayına bağlamak. Ekran her göründüğünde yeni bir nesne doğar.
- Kayıt için doğal bir kimlik varken bunu benzersiz yapmamak. Yeniden deneme çiftlenir.
- Benzersizlik kısıtının çakışmayı çözdüğünü sanmak. Çakışmada kazananı sıra belirler.
- Kaydetmeyi unutmak. Değişiklik bellektedir ve uygulama kapanınca kaybolur.
- Bağlamı iş parçacıkları arasında paylaşmak. Çerçeve bunu desteklemez; her iş parçacığı kendi bağlamını kullanır.
- Şemayı değiştirip eski veriyle denemeden yayımlamak.
Sınırlayıcı Ölçüm
Kısıtın katkısını ayırmak için aynı kayıt çiftini kısıt kapalıyken ölçelim. Kısıt kapalıysa aynı anahtarla gelen kayıt mevcut kaydın yerine geçmez; yeni bir kayıt olur.
// main.swift — kisit acik ve kapali
let openSame = replay("kisitsiz-ayni-deger", from: ContextBox(enforcesUnique: false), saving("a1", key: "k1"), then: saving("a1", key: "k1")).report()
let openTwo = replay("kisitsiz-iki-deger", from: ContextBox(enforcesUnique: false), saving("a1", key: "k1"), then: saving("b2", key: "k1")).report()
let closedSame = replay("kisitli-ayni-deger-yeniden", from: ContextBox(), saving("a1", key: "k1"), then: saving("a1", key: "k1")).report()
countSensitivities([openSame, openTwo, closedSame])
kisitsiz-ayni-deger | bir=k1=a1 iki=k1=a1,k1=a1 ab=k1=a1,k1=a1 ba=k1=a1,k1=a1 -> yinelemeye-duyarli kisitsiz-iki-deger | bir=k1=a1 iki=k1=a1,k1=a1 ab=k1=a1,k1=b2 ba=k1=b2,k1=a1 -> cift-duyarli kisitli-ayni-deger-yeniden | bir=k1=a1 iki=k1=a1 ab=k1=a1 ba=k1=a1 -> saglam saglam: 1 yinelemeye-duyarli: 1 siraya-duyarli: 0 cift-duyarli: 1
Aynı kayıt çifti, kısıt kapalıyken yinelemeye duyarlı, açıkken sağlam çıktı. İki değerli çift ise kısıtsız çift duyarlı, kısıtlı sıraya duyarlıydı. Kısıt yinelemeyi çözdü ve iki satırı bir satıra indirdi; sırayı çözmedi.
Bu ölçümün sınırı KL12’dir. Model, çakışmada son yazanın kazanacağını varsayar. Gerçek çerçevede birleştirme politikası ayarlanabilir ve bazı politikalar mevcut kaydı korur; bu derste hiçbiri ölçülmedi. Politika değişirse ab ile ba yer değiştirebilir, ama ikisinin eşit olmayacağı sonucu değişmez.
Gerçek Uygulamada Nerede Görülür
Nesne kalıcılık çerçeveleri, bir nesne grafiğini sizin yerinize satırlara çevirir; bu rahatlık, yinelemenin görünmez hâle gelmesinin de nedenidir. Kullanıcı bir not oluşturduğunda uygulama yeni bir nesne yaratır ve bağlamı kaydeder. Aynı kod yolu bir yeniden denemede ya da bir ekran yenilemesinde tekrar çalışırsa, çerçeve her seferinde yeni bir nesne üretir. olustur-ayni-deger satırında gördüğümüz çoğaltma budur: iki çağrı, iki nesne. Liste ekranında aynı not iki kez durur ve hiçbir hata verilmemiştir.
Benzersizlik kısıtı bu çoğaltmayı engeller. Bir nitelik benzersiz olarak işaretlenince, aynı değerli ikinci nesne eklenemez; çerçeve ya yeni nesneyi var olanla birleştirir ya da kaydı reddeder. kisitli-ayni-deger satırında iki çağrı tek nesne verir ve sınıf sağlam olur. Ama kısıt, iki farklı değerin aynı benzersiz anahtarla gelmesini çözmez: kisitli-iki-deger satırında kalan değer sıraya bağlıdır. Kısıt yinelemeyi çözer, sırayı çözmez.
Gerçek bir uygulamada bu, sunucudan gelen kayıtların yerel kopyayla eşleştirilmesinde görülür. Sunucu her kaydın kararlı bir kimliğini verir; yerel kayıt bu kimlikle benzersiz kılınırsa, aynı yanıtın iki kez işlenmesi bir kayıt üretir. Kimlik olmadan yerel nesne yeniden oluşturulur ve iki kopya doğar.
Yanlış Kurulum ve Düzeltmesi
Yaygın yanlış kurulum, benzersizliği kodda kontrol etmektir: “bu değerli nesne var mı, yoksa oluştur”. Kontrol ile oluşturma arasında kalan aralık, iki işlemin ikisinin de “yok” yanıtı alabildiği yerdir. Ayrıca kontrol yalnız bağlamdaki nesnelere bakar; başka bir bağlamda ya da henüz kaydedilmemiş bir nesne gözden kaçar.
Düzeltme, benzersizliği şemanın bir parçası yapmaktır. Kısıt depolama katmanında durur ve her yazmada uygulanır; kodun hangi yoldan geldiği önemli olmaz. Çerçevenin birleştirme politikası da burada belirleyicidir. Politika, çakışmada hangi değerin kalacağını söyler. Modelimiz son yazanın kazandığını varsaydı, ve bu KL12 olarak kayıtlıdır. Başka bir politika mevcut kaydı korursa ab ile ba yer değiştirir; ama iki sıranın aynı sonucu vermediği gerçeği değişmez.
İkinci yanlış, kaydetmeyi unutmaktır. Nesneyi oluşturup bağlamı kaydetmeyen kod, değişikliğin kalıcı olduğunu sanır. Uygulama kapanınca nesne kaybolur. Modelimiz her işlemi ekleyip hemen kaydeder; KL13 bunu kaydeder. Gerçek kodda kaydetme çağrısını kimin yaptığı, iş yapan her yerde açıkça belirlenmelidir.
KAYNAK Parçayı Okumak
Dersin başındaki adsız parça, bir nesne tipini tanımlar, bir bağlam açar, nesneyi oluşturur ve kaydeder. Nesne tipi, kalıcı olacak niteliklerini belirtir ve çerçeve bunları tabloya çevirir. Parçanın gösterdiği asıl şey, sizin “kaydet” dediğiniz çağrının aslında bir işlem sınırı olmasıdır: bu çağrıya kadar yapılan değişiklikler bellekte durur ve çağrıyla birlikte diske gider.
Model bunu tek adıma indirgedi ve kaydedilmemiş değişiklikleri, geri almayı ve birden çok bağlamı kurmadı. Bunlar gerçek uygulamada önemli durumlardır; ancak ölçtüğümüz soru, yani aynı işlemin yinelenmesi ve sırasının değişmesi, bu sadeleştirmeyle de anlamlı kalır. Şema göçü kısmında anlatılan sürüm değişikliği ise ayrı bir konudur ve bu derste yalnız anlatılır, ölçülmez.
Ölçümden Tasarıma
Nesne çerçevesinin ölçümü, kısıtın ne yaptığını ve ne yapmadığını ayırır. Kısıtsız durumda yinelenen oluşturma çoğaltma yaratır; kısıtla aynı çağrılar tek nesne verir. Ama kısıt yalnız eşitliği çözer. Aynı anahtarla gelen iki farklı değerde hangisinin kalacağı, çerçevenin birleştirme politikasına bağlıdır. Dolayısıyla tasarım iki kararı ayrı ayrı vermeyi gerektirir: hangi nitelik kimliği belirler ve çakışmada hangi değer kalır.
Kimlik seçimi bir veri modeli kararıdır. Sunucudan gelen kayıtlar için sunucunun verdiği kimlik doğal adaydır. Kullanıcının oluşturduğu kayıtlar için uygulama bir kimlik üretir ve bu kimliği oluşturma anında, bir kez, kaydın kendisine yazar. Kimliği her çağrıda yeniden üretmek, kısıtı anlamsız kılar, çünkü her çağrı farklı bir kimlik taşır ve hiçbir çakışma doğmaz.
Politika kararı ise ürün kararıdır. Son yazanın kazanması basittir ve veri kaybı riski taşır; mevcut kaydı korumak güvenlidir ama yeni bilgiyi atar; alan alan birleştirmek en iyi sonucu verir ve en çok iş ister. Hangisinin doğru olduğunu ölçüm söylemez. Ölçüm yalnız seçilen politikanın hangi sınıfa yol açtığını gösterir.
Bir de şema göçü vardır. Veri tipi sürümler arasında değişir ve eski sürümün yazdığı veri yeni şemaya taşınmalıdır. Basit eklemeler çerçeve tarafından otomatik yapılabilir; ad değişikliği ve birleştirme açık bir eşleme ister. Göç, her sürümde bir kez koşan bir işlemdir. Yinelenirse ya da yarıda kesilirse, veri ya eski ya yeni biçimde kalmalıdır; yarım bir göç, ölçtüğümüz yinelemeye duyarlılığın en pahalı biçimidir.
Ölçümün dışında kalan bir durumu da not etmek gerekir: ilişkili nesneler. Bir not ile etiketleri arasında ilişki varsa, notu silmek etiketlere ne yapar, etiketi silmek nota ne yapar? Çerçeve bu sorunun cevabını silme kuralı olarak ister: ilişkiyi kopar, bağlı nesneleri de sil ya da silmeyi reddet. Kural verilmezse varsayılan davranış devreye girer ve çoğu zaman yetim nesneler kalır. Bu dersin modeli ilişkileri kurmaz; ama gerçek bir şemada bu karar, benzersizlik kısıtı kadar önemlidir ve aynı özenle yazılmalıdır.
Silme kuralının ölçümle bağlantısı doğrudandır. Bir silme işlemi yinelenirse, ikinci silme zaten olmayan bir nesneyi hedefler; çerçevenin bu durumda hata verip vermediği, silmenin yinelemeye karşı sağlam olup olmadığını belirler. Bunu ölçmeden varsaymamak, bu dersin genel alışkanlığıdır.
Özet
- Nesne kalıcılık çerçevesi, nesneleri bir bağlamda izler ve kaydedildiğinde depoya yazar.
- Aynı değerle yeni nesne oluşturmak yinelendiğinde iki nesne bırakır (
obj1=a1sonraobj1=a1,obj2=a1); sınıf yinelemeye duyarlıdır. - Benzersizlik kısıtlı kayıt aynı değerde sağlamdır, aynı anahtara farklı değerde sıraya duyarlıdır (
abb2,baa1). - Kısıt kaldırılırsa aynı kayıt çifti yinelemeye duyarlı hâle gelir; iki değerli çift çift duyarlı olur.
- Şema göçü ve birleştirme politikası ölçülmedi; KAYNAK ve düzyazı olarak kaldı.
- Bu dersin duyarlılığı: yinelemeye duyarlı.
Sonraki Adım
Şimdiye kadar tüm veri tek bir aygıtta duruyordu. Sıradaki derste, Bulut Eşitleme, aynı kaydı iki aygıtın yazdığı durumu ölçeceğiz ve aygıtların değerlerinin farklı sırayla gelmesinin sonucu nasıl değiştirdiğini göreceğiz.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.