Soruya verilen cevap.
Objective-C manuel retain/release döngüleri ve zayıf referanslar için doğrudan işaretçiler kullanıyordu, bu da her nesne erişiminde önemli performans kayıplarına neden olan çalışma zamanı swizzling veya genel hash tabloları gerektiriyordu. Apple Swift'i tasarladığında, zayıf referansların otomatik olarak nil olmasını destekleyen bir otomatik bellek yönetim modeli gerektiriyordu; böylece zayıf referanslarla asla karşılaşmayan nesneler üzerinde yük oluşturulmadan, referans edilen nesne serbest bırakıldığında otomatik olarak nil oluyordu. Bu gereklilik, yalnızca gerektiğinde zayıf referans meta verilerini dışa aktaran bir yan tablo mimarisinin geliştirilmesine yol açtı.
Merkezi sorun, bellek verimliliği ile güvenlik arasında denge kurmaktı. Her nesne başlığında zayıf referans takibi için doğrudan depolama (örneğin, zayıf işaretçilerin bağlı listesi veya doğrudan zayıf sayım) bulundurulursa, her sınıf örneğinin bellek ayak izi önemli ölçüde artacak, yalnızca güçlü referanslar kullanan performans kritik kod üzerinde olumsuz etkiler yaratacaktı. Tersine, zayıf referansların nesne adresine göre anahtarlanan bir genel hash tablosunda depolanması senkronizasyon darboğazları ve nesneler serbest bırakıldığında karmaşık kurtarma mantığı gerektiriyordu. Zayıf referans içermeyen nesneler üzerinde sıfır maliyet yükleyen ve son güçlü referans kaybolduğunda iş parçacığı güvenli atomik sıfırlama garantisi veren bir mekanizma oluşturma zorluğu vardı.
Swift, her sınıf örneği başlığının ayrı bir yığın tahsis edilmiş yan tablo yapısına nullable bir işaretçi içerdiği bir yan tablo sistemi kullanır. Bu yan tablo, zayıf referans sayısını ve nesneye geri işaretçi depolar; zayıf referanslar gerçekte bu yan tabloya işaret eder, nesneye doğrudan değil. Güçlü referans sayısı sıfıra ulaştığında, çalışma zamanı atomik olarak yan tablodaki nesne işaretçisini nil olarak ayarlar, bu da tüm mevcut zayıf referansların bir sonraki erişimde nil gözlemlemesine neden olurken, nesnenin belleği zayıf referans sayısı sıfıra ulaşana kadar tahsis edilmeye devam eder; bu noktada hem yan tablonun hem de nesne belleği geri alınır.
Hayattan bir durum
ViewController örneklerinin kullanıcı avatarlarını indirdiği ve görüntülediği yüksek çözünürlüklü bir resim işleme hattı geliştirdiğinizi hayal edin. Gereksiz ağ isteklerini önlemek için, aynı avatarı görüntüleyen birden fazla view controller'ın altındaki bellek tamponunu paylaşabilmesi için indirilen UIImage nesnelerine referansları depolayan bir ImageCache singleton'u uyguluyorsunuz.
Düşünülen bir yaklaşım, keyfi tahliye politikalarına sahip bir NSCache içerisinde güçlü referanslar depolamaktı. Bu, hemen erişim ve tür güvenliği sağlarken, her resmi süresiz olarak tutan cache nedeniyle ciddi bellek sızıntılarına neden oldu ve sonunda uzun süreli kaydırma oturumları sırasında belleği uyarıları ve uygulama sonlandırmalarını tetikledi. Avantajları arasında basitlik ve hızlı erişim bulunsa da, sınırsız bellek büyümesinin dezavantajları üretim için uygun hale gelmesini engelledi.
Bir başka yaklaşım, view controller'ların belirli girişleri kaldırmak için cache'i delege protokolü aracılığıyla serbest bırakıldığında bildirdiği manuel bir gözlemci paterni uygulamaktı. Bu, teorik olarak sızıntıları önlese de, görünüm katmanı ile önbellek katmanı arasında kırılgan bir sıkı bağlılık oluşturdu, hızlı geçiş geçişleri sırasında yarış koşullarını ele almak için kapsamlı şablon kodu gerektirdi ve bildirim mesajları kaçırıldığında veya geç iletildiğinde çökme riskini artırdı.
Seçilen çözüm, cache uygulamasındaki Swift'in yerel zayıf referanslarını kullandı:
class ImageCache { private var cache: [URL: WeakBox<UIImage>] = [:] func image(for url: URL) -> UIImage? { return cache[url]?.value } func setImage(_ image: UIImage, for url: URL) { cache[url] = WeakBox(value: image) } } final class WeakBox<T: AnyObject> { weak var value: T? init(value: T) { self.value = value } }
WeakBox sarmalayıcısı aracılığıyla cache sözlüğü değerlerini zayıf olarak bildirerek, ImageCache bir resmin hala bellekte var olup olmadığını kontrol edebilirken, aynı zamanda aktif olarak o avatarı gösteren hiçbir view controller olmadığında otomatik geri alma sağlar. Bu, hem bellek sızıntılarını hem de manuel muhasebe yükünü ortadan kaldırarak, hızlı kaydırma sırasında pik bellek kullanımında %40'lık bir azalma sağlamış ve sistemin bellek izleyicisi tarafından sonlandırmayı önlemiştir.
Adayların genellikle kaçırdığı noktalar
Zayıf bir referansa erişimin neden güçlü bir referansa erişimden daha yavaş olabileceği ve bu performans farkının hangi özel koşulda ölçülebilir hale geldiği?
Zayıf bir referansa erişim, nesne başlığında depolanan yan tablo işaretçisini çözüme ulaştırmayı ve daha sonra o yan tablodan atomik olarak nesne işaretçisinin yüklenmesini gerektirir; bu, sıfırlanıp sıfırlanmadığını kontrol etmek için. Aşırı yük minimum düzeyde olsa (tipik olarak tek bir ek dolaylılık), zayıf bir referans üzerinden her öğenin sıkı döngülerde erişildiği büyük koleksiyonların (binlerce öğe) üzerinden geçerken ölçülebilir hale gelirken, güçlü referanslar yalnızca atomik garantiler olmaksızın tek bir işaretçi takibi gerektirir.
Uygulama düzeyinde zayıf bir referansı unowned referansından ayıran nedir ve bir nesne serbest bırakıldıktan sonra unowned bir referansa erişmeye çalışmanın neden bir çalışma zamanı çökmesine neden olduğu, nil vermez?
Zayıf referanslar sıfırlamayı sağlamak için yan tablolar kullanırken, unowned referanslar (varsayılan güvenli modda) de yan tabloyu referans alır ancak unowned referans var olduğu sürece nesnenin tahsis edilmiş kalacağını varsayar, nesne serbest bırakıldığında çökmesine neden olur çünkü yan tablo girişi yok edilmiş olarak işaretlenmiştir ancak nil olmamıştır. Adaylar genellikle, güvensiz unowned referansların yan tabloyu tamamen atladığını ve serbest bırakıldıktan sonra erişildiğinde belleği bozacak şekilde değiştiğini kaçırır; oysa güvenli unowned referanslar en azından yan tablonun yok edilme bitine göre belirleyici bir hata verir.
Bir nesne örneğinin belleği, deinit'i tamamladıktan ve tüm güçlü referanslar kaybolduktan sonra neden yığın içerisinde tahsis edilmeye devam eder ve bu bellek ne zaman gerçekten serbest bırakılır?
Bellek, yan tablo zayıf referans sayısını koruduğu için kalır; nesne başlığı ve onunla ilişkili depolama, zayıf sayım sıfıra ulaşana kadar geri alınamaz, bu da zayıf referansların geri kazanılmış belleğe işaret etmemesini sağlar. Sadece son zayıf referans yok edildiğinde (zayıf sayıyı sıfıra düşürerek), çalışma zamanı hem yan tabloyu hem de nesne bellek bölgesini tahsis eder, bu süreç geliştiricilere görünmez ancak kullanımdan sonra serbest bırakma güvenlik açıklarını önlemek için kritik öneme sahiptir.