Bir nesne sözlük anahtarı olduğu sürece eşitlik ve karma hesaplamasında kullanılan özellikleri sabit tutun. Mac’inizde yerel bir Swift hesaplaması için, aramanın ne anlama gelmesi gerektiğine göre nesne kimliğini, değişmez bir ID’yi veya değer anlık görüntüsünü seçin.
Bu kılavuzda tamamen bellekte tutulan yapay dikdörtgenler ve küçük alan hesaplamaları kullanılıyor. Amaç, dikdörtgenin etiketi veya boyutları değiştiğinde ilişkili sözlük girdisini kaybetmemek ve başarılı bir aramayı güncel bir hesaplamayla karıştırmamaktır.
Bir nesneyi değiştirmek sözlük aramasını neden bozabilir?
Swift’in Dictionary türü, anahtarları Hashable uyumlu olan bir karma tablosudur. Anahtar tabanlı alt simge, istenen anahtar bulunmayabileceğinden isteğe bağlı bir değer döndürür. Karma değeri girdiyi bulmaya yardımcı olur; eşitlik ise hangi anahtarların eşdeğer sayılacağını belirler. Apple’ın Dictionary belgelerine bakın.
Hashable sözleşmesi bu işlemleri birbirine bağlar: eşit örnekler, Hasher türüne aynı temel bileşenleri aynı sırayla vermelidir. Özel bir anahtar uygularken, hash(into:) içinde eşitliği tanımlayan özellikleri kullanın. Apple bu gereksinimi Hashable başlığında açıklar.
Başvuru türündeki bir anahtar başka bir noktayı da gündeme getirir. Bir sınıf örneğini atamak, bağımsız bir nesne oluşturmak yerine mevcut nesneye bir başvuru aktarır. Başvuruyu let olarak bildirmek, değiştirilebilir özelliklerini dondurmaz. Bunlar, Structures and Classes başlığında belgelenen dil kurallarıdır.
Düzenlenebilir etiketini karşılaştıran ve karma değerini bu etiketten hesaplayan bir dikdörtgen sınıfı düşünün. Nesneyi etiketi "Tile A" iken eklersiniz, sonra etiketi "Tile B" olarak değiştirirsiniz. Saklanan başvuru artık anahtarı tanımlayan farklı bir durumu gösterir. Sözlüğün ilk yerleşimi ile sonraki arama arasında artık kararlı bir uyum yoktur.
Tasarım kuralı, karma tablosu ve eşitlik sözleşmelerinden çıkar: anahtarın eşitliğini tanımlayan durumu, anahtar sözlükte bulunduğu sürece sabit tutun. Apple, Foundation nesneleri için bununla ilgili gereksinimi açıkça belirtir: bir nesnenin karması, bu karmayı yerleşim için kullanan bir koleksiyona ait olduğu sürece değişmemelidir. NSObjectProtocol.hash başlığına bakın.
Geçersiz bir değişikliğin her durumda anında nil döndüreceği varsayımına dayalı bir test yazmayın. Anahtar sözleşmesi bozulduktan sonra, aramanın görünüşte başarılı olması tasarımın işe yaradığını kanıtlamaya yetmez. Bunun yerine amaçlanan eşitlik kurallarını ve kararlı davranışı test edin.
Göreve hangi anahtar uyar?
Uyumluluk tanımı yazmadan önce sözlük girdisinin ne anlama geleceğini belirleyin. Dikdörtgen alanı aracında şu üç sorunun her biri farklı bir tasarıma götürür:
| Bir girdi neyi temsil etmeli? | Uygun anahtar | Dikdörtgen değiştiğinde ne olur? |
|---|---|---|
| Bu belirli dikdörtgen nesnesini | Kimlik tabanlı sınıf anahtarı | Girdi o örnekle ilişkili kalır |
| Mevcut nesnesinden bağımsız olarak tanımlanan mantıksal bir dikdörtgeni | Değişmez ID değeri | Girdi ID ile ilişkili kalır |
| Belirli boyutlar için yapılan bir hesaplamayı | Değişmez boyut anlık görüntüsü | Boyutlar değişince farklı bir arama anahtarı kullanılır |
Bu tablo, Swift’in kimlik, değer ve karma kurallarından türetilmiş bir tasarım yardımcısıdır. Ayrıca iki ayrı gereksinimi birbirinden ayırır: ilişkilendirmeye erişilebilir kalması ve hesaplanan sonucun güncel olması. Kararlı bir kimlik anahtarı ilkini sağlar; yeniden hesaplama veya tüm girdilerin anlık görüntüsünü tutmak ikincisini karşılar.
Girdi tek bir örneğe aitse nesne kimliğini kullanın
Düzenlemeler boyunca tek bir nesneyi izlemesi gereken bellek içi bir ilişkilendirme için eşitliği === ile tanımlayın ve ObjectIdentifier değerinin karmasını alın. Swift’in kimlik operatörü, başvuruların aynı örneğe işaret edip etmediğini sınar. Apple, ObjectIdentifier türünü, karşılaştırmaları örneğin yaşam süresi boyunca geçerli olan karma uyumlu bir tanımlayıcı olarak belgeler. Structures and Classes ve ObjectIdentifier başlıklarına bakın.
Aşağıdaki örnek, tamamlanmış ve eşzamanlı çalışan bir programdır. Swift komut satırı araçlarının yüklü olduğu bir Mac gerekir; otomatik olarak içe aktarılan Swift standart kitaplığını kullanır. Programı IdentityKeys.swift adıyla kaydedin ve Terminal’de swift IdentityKeys.swift komutunu çalıştırın. Örnekler incelendi, ancak araştırma ortamında Swift bulunmadığı için çalıştırılamadı.
final class Rectangle: Hashable {
var label: String
var width: Int
var height: Int
init(label: String, width: Int, height: Int) {
self.label = label
self.width = width
self.height = height
}
static func == (lhs: Rectangle, rhs: Rectangle) -> Bool {
lhs === rhs
}
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}
func demonstrateIdentityKeys() {
let rectangle = Rectangle(label: "Tile A", width: 4, height: 3)
let alias = rectangle
let separate = Rectangle(label: "Tile A", width: 4, height: 3)
var areas: [Rectangle: Int] = [
rectangle: rectangle.width * rectangle.height
]
rectangle.label = "Tile B"
assert(areas[alias] == 12)
assert(areas[separate] == nil)
rectangle.width = 5
assert(areas[rectangle] == 12) // Still the previous calculation.
areas[rectangle] = rectangle.width * rectangle.height
assert(areas[rectangle] == 15)
assert(areas.count == 1)
print("Renamed lookup: 12")
print("Recalculated area: \(areas[rectangle]!)")
}
demonstrateIdentityKeys()
Beklenen çıktı önce Renamed lookup: 12, ardından Recalculated area: 15 olur. Onaylamalar yararlı sözleşmeyi ifade eder: bir takma ad mevcut girdiyi bulur, ayrı oluşturulmuş bir dikdörtgen bulamaz ve ilk nesneyi düzenlemek yeni bir anahtar oluşturmaz.
Genişlik değişikliği, program 15 ataması yapana kadar saklanan alan değerini bilerek 12 olarak bırakır. Kimlik kararlı kalır, ancak sözlük aritmetik bağımlılıklarını izlemez veya değerleri yeniden hesaplamaz. Uygulamanızın düzenlemeden hemen sonra değeri güncelleyip güncellemeyeceğine ya da sonraki hesaplamaya kadar değeri kaldırıp kaldırmayacağına karar verin.
Kimlik tabanlı eşitlik, tür genelinde verilen bir karardır. Bu sınıftaki her == karşılaştırması artık “aynı örnek” anlamına gelir. Modeliniz eşitliği zaten boyutlara veya başka bir değere göre tanımlıyorsa, sırf tek bir sözlük için bu anlamı değiştirmek yerine ayrı bir anahtar tasarımı kullanın.
Sözlüğün anahtarı olarak doğrudan ObjectIdentifier da kullanabilirsiniz, ancak bu durumda tanımlayıcı tek başına sonraki işlemler için gereken dikdörtgeni sağlamaz. Tanımlayıcısı karşılaştırma için kullanıldığı sürece ilgili nesneyi bellekte tutun ve artık gerekmeyen girdileri kaldırın. Belgelerdeki yaşam süresi sınırı, bunun kapsamı belirli, bellek içi bir kimlik mekanizması olduğu anlamına gelir. ObjectIdentifier başlığına bakın.
Girdi bir hesaplamayı temsil ediyorsa değer anlık görüntüsü kullanın
Yeniden kullanılabilir alan sonuçları için çoğu zaman uygun anahtar, boyutların kendisidir. 4 ile 3 ölçülerindeki iki dikdörtgen, etiketleri farklı ve ayrı nesneler olsalar da aynı sonucu paylaşabilir. Boyutlar değiştiğinde farklı bir girdi seçilmelidir.
Swift yapıları değer semantiğine sahiptir ve saklanan özellikleri karma uyumlu olduğunda derleyici Hashable uyumluluğunu sentezleyebilir. Bu nedenle değişmez tamsayılar içeren bir anlık görüntü, bu hesaplama için kolayca kullanılabilecek bir anahtar sağlar. Structures and Classes ve Hashable başlıklarına bakın.
Bu tamamlanmış alternatifi ayrı olarak swift SnapshotKeys.swift komutuyla çalıştırın. Yapay girdiler küçük, pozitif tamsayılardır; bu yüzden örneğin genel amaçlı bir sayısal girdi arayüzüne ihtiyacı yoktur.
final class EditableRectangle {
var label: String
var width: Int
var height: Int
init(label: String, width: Int, height: Int) {
self.label = label
self.width = width
self.height = height
}
}
struct AreaInputs: Hashable, Sendable {
let width: Int
let height: Int
}
func demonstrateSnapshotKeys() {
let rectangle = EditableRectangle(
label: "Tile A", width: 4, height: 3
)
var areas: [AreaInputs: Int] = [:]
let original = AreaInputs(
width: rectangle.width, height: rectangle.height
)
areas[original] = original.width * original.height
rectangle.label = "Tile B"
rectangle.width = 5
let current = AreaInputs(
width: rectangle.width, height: rectangle.height
)
assert(areas[original] == 12)
assert(areas[current] == nil)
areas[current] = current.width * current.height
assert(areas[current] == 15)
assert(areas.count == 2)
print("Original area: \(areas[original]!)")
print("Current area: \(areas[current]!)")
}
demonstrateSnapshotKeys()
Beklenen çıktı önce Original area: 12, ardından Current area: 15 olur. Program iki farklı anlık görüntü eklediği için her iki sonuca da erişilebilir. Eski sonuç, eski boyutları doğru biçimde temsil etmeye devam eder.
Hesaplamayı etkileyen her girdiyi anlık görüntüye ekleyin. Etiket dikdörtgenin alanını etkilemediğinden burada yer almıyor. Sonraki bir hesaplama başka bir ayara bağlıysa bu ayarı da anahtara ekleyin. Aksi hâlde farklı hesaplamalar yanlışlıkla aynı girdiyi paylaşabilir. Bu, anahtarın Hashable gereksinimini karşılayıp karşılamasından bağımsız bir hesaplama tasarımı gereksinimidir.
Yalnızca değiştirilebilir bir sınıf başvurusunu sarmalayan bir yapı, o nesnenin dondurulmuş bir anlık görüntüsünü kendiliğinden oluşturmaz. Bu örnekte yapıldığı gibi, ilgili değerleri anlık görüntüye kopyalayın. Tekrarlanan düzenlemeler artık ihtiyaç duymadığınız sonuçları biriktirecekse eski anlık görüntüleri ne zaman sileceğinize de karar verin.
Değişmez bir ID’yi ne zaman kullanmalısınız?
Birden çok nesne örneğinin aynı mantıksal girdiye erişmesi gerekiyorsa ID kullanın. Bu yapay araçta değişmez bir tamsayı ID yeterli olabilir: sonuçları [Int: Int] içinde saklayın, erişim için dikdörtgenin let id: Int özelliğini kullanın, düzenlenebilir etiketleri ve boyutları anahtarın dışında tutun.
Bu tasarımda ID’si 7 olan iki nesne bilerek aynı sözlük girdisine erişir. Oluşturma kurallarınız, farklı mantıksal dikdörtgenlere yanlışlıkla aynı ID’nin verilmemesini sağlamalıdır. Dictionary belgelerinde açıklandığı üzere, mevcut bir anahtar güncellendiğinde ilişkili değer değişir.
ID tabanlı bir alan tablosu, boyutlar değiştikten sonra da bir güncelleme politikası gerektirir. Her farklı girdi kümesinin sonuçlarını istiyorsanız anlık görüntü kullanın. Her mantıksal dikdörtgen için yalnızca en son sonucu istiyorsanız, ID ile açıkça yeniden hesaplama daha uygundur.
Foundation sözlükleri veya aktörler değiştirilebilir anahtar sorununu çözer mi?
Foundation’ın ekleme sözleşmesi farklıdır. NSMutableDictionary.setObject(_:forKey:), anahtarın NSCopying uyumlu olmasını gerektirir ve ekleme sırasında anahtarın bir kopyasını alır. Kopyalama uygulaması anahtar durumunu kararlı hâle getiriyorsa anahtar kopyası bir anlık görüntü oluşturabilir. Sonraki aramalar bu saklanan kopyayla eşleşmelidir. Apple’ın setObject(_:forKey:) belgelerine bakın.
Bu davranıştan, Swift’in yerel sözlüğünün anahtar olarak kullanılan sınıf örneğinin bağımsız bir kopyasını oluşturduğu sonucunu çıkarmayın. Swift’te sınıf atamaları başvuru semantiğine sahiptir. Özel Foundation anahtarları için kopyalama, eşitlik ve karma davranışlarını birlikte doğrulayın; Foundation’ın hash gereksinimi hâlâ geçerlidir.
Eşzamanlılık ayrı bir konudur. Aktörler, yalıtılmış değiştirilebilir durumlarına erişimi koordine eder; Sendable ise gereksinimleri karşılayan değerlerin eşzamanlılık alanları arasında aktarılabileceğini belirtir. Bunların hiçbiri anahtarın eşitlik anlamını değiştirmez veya ekleme sonrasında tanımlayıcı durumu değişen bir anahtarı düzeltmez. Swift’in Concurrency bölümü ile Apple’ın Sendable belgelerine bakın.
Anlık görüntü örneğinde açıkça Sendable uyumluluğu tanımlanır; çünkü saklanan tamsayılar protokolün değer türlerine ilişkin gereksinimlerini karşılar. Alan hesaplamalarını daha sonra bir aktöre taşırsanız eksiksiz bir anlık görüntü gönderin ve aktörün sonuç sözlüğünün sahibi olmasını sağlayın. Değiştirilebilir dikdörtgen mevcut yalıtım alanında kalabilir. Bu eşzamanlı örneklerin aktöre ihtiyacı yoktur.
Bir aramaya güvenmeden önce sözleşmeyi kontrol edin
Seçtiğiniz tasarımı doğrulamak için küçük, belirleyici bir işlem dizisi kullanın:
- Bir girdi ekleyin ve başlangıçta bulunabildiğini doğrulayın.
- Eşitlik ve karma hesabına katılmayan bir özelliği değiştirin; aynı girdiye hâlâ erişilebildiğini doğrulayın.
- Bir takma adı ayrı oluşturulmuş bir nesne veya anlık görüntüyle karşılaştırın; sonuçların amaçlanan eşitlik anlamıyla uyuştuğunu doğrulayın.
- Bir hesaplama girdisini değiştirin; sonucun yenilendiğini, kaldırıldığını veya yeni bir anlık görüntü üzerinden erişildiğini doğrulayın.
- Değiştirme ve eklemenin beklendiği gibi çalıştığını görmek için sözlük sayısını kontrol edin.
Mevcut bir uygulama anahtarı tanımlayan durumu değiştiriyorsa, sonraki aramalara güvenmeden önce anahtar modelini düzeltin. Bu küçük yerel hesaplama için sonuçları ayrı bir yapay girdi listesinden yeniden oluşturmak, sonraki sözlük işlemlerini bir onarım yöntemi olarak kullanmaktan daha açıktır. Bulamadığınız bir girdiyi sessizce kaybetmek yerine yeniden hesaplamak için yeterli özgün girdiyi saklayın.
Yaşayan tek bir nesneyle ilişkilendirme için kimliği, tek bir mantıksal öğe için değişmez ID’yi, tek bir hesaplama içinse değer anlık görüntüsünü seçin. Ardından sonuçların güncellenme politikasını açıkça belirleyin. Bu kararlar birlikte, düzenlenebilir dikdörtgen değişirken aramaların anlamlı kalmasını sağlar.
Kaynaklar
- Hashable — Apple
- Dictionary — Apple
- Structures and Classes — Swift.org
- ObjectIdentifier — Apple
- hash — Apple
- setObject(_:forKey:) — Apple
- Concurrency — Swift.org
- Sendable — Apple