Swift’in başlatma modeli, Objective-C gibi dillerde sıkça görülen tanımsız davranışı ortadan kaldırmak için tasarlanmıştır; tüm belleğin başlatılmadan önce örnek yöntemlerine veya özelliklerine erişim, segmentasyon hatalarına veya güvenlik açıklarına yol açabilir. Temel sorun sınıf hiyerarşilerindedir: bir alt sınıf nesnesi, kendi saklanan özelliklerine ek olarak tüm miras alınan özellikler için belleği içerir ve derleyici, bu belleğe hiçbir kodun dokunmadığından emin olmalıdır; her bir byte geçerli olana kadar. Bunu çözmek için Swift, statik analiz yoluyla kesin başlatma (DI) invariantını uygular; bir nesnenin, iki aşamalı başlatmanın 1. aşaması tamamlanana kadar kısmen inşa edilmiş, tehlikeli bir durumda kalmasını şart koşar. 1. aşamada başlatıcı, mevcut sınıf tarafından tanıtılan tüm özelliklere değer atamalı ve yukarıdaki sınıf başlatıcılarına devretmelidir; yalnızca bu aşama tamamlandıktan sonra self güvenli bir şekilde erişilebilir veya kaçırılabilir.
class Vehicle { let wheelCount: Int init(wheels: Int) { self.wheelCount = wheels // Vehicle için 1. aşama tamamlandı } } class Bicycle: Vehicle { let hasBell: Bool init(bell: Bool) { // 1. Aşama: Önce kendi özelliklerini başlat self.hasBell = bell // Sonra üst sınıfa delege et super.init(wheels: 2) // 1. aşama tamamlandı: tam kesin başlatma başarıyla gerçekleştirildi // 2. Aşama: self kullanmak güvenli self.checkSafety() } func checkSafety() { print("\(wheelCount) tekerlekli bisiklet \(hasBell ? "zil var" : "zil yok")") } }
Bir tıbbi kayıt uygulaması geliştirirken, başlatma sırasında hastanın yaşı (üst sınıf özelliği) temelinde bir şiddet puanının hesaplanması gereken karmaşık bir senaryoyla karşılaştık. İlk uygulama, super.init(age:) çağrısından önce self.age'e erişim sağlayan bir yardımcı yöntemi calculateSeverity() çağırmayı denedi ve bu, alt sınıf başlatıcısının miras alınan belleğin güvenliğini henüz garanti etmediğinden derleyici hatasına neden oldu. Bu kısıtlamayı çözmek için üç farklı mimari yaklaşım değerlendirdik.
Bir yaklaşım, şiddet puanını açıkça sarılmış opsiyonel (var severity: Int!) olarak tanımlamak ve üst sınıf başlatma tamamlandıktan sonra atamasını ertelemekti. Bu, derleyiciyi tatmin etmesine rağmen, önemli bir çalışma zamanı riski getirdi: özellik atanmadan erişilebilir hale gelebilir, bu da çöküşe neden olabilirdi ve kayıt bütünlüğü garantisini tehlikeye atarak değişmez bir let bildirimini kullanmamıza engel oldu.
İkinci bir strateji, geçici bir yer tutucu nesneyi yalnızca yaşı okumak, şiddeti çevrimdışı hesaplamak ve ardından önceden hesaplanmış değerlerle gerçek örneği oluşturmak için kullanmayı düşündü. Bu bellek güvenliğini korumaya devam etti ancak önemli bir boilerplate ekledi ve başlatma akışını belirsiz hale getirdi, kod tabanını diğer ekip üyeleri için bakım ve hata ayıklama açısından önemli ölçüde zorlaştırdı.
Seçilen çözüm, başlatıcıyı yaşı bir parametre olarak kabul edecek şekilde yeniden yapılandırmak, şiddeti girdi parametresi üzerinde işlem yapan saf bir statik işlev kullanarak hesaplamak ve önceden hesaplanmış değeri belirli bir başlatıcıya iletmek oldu. Bu yaklaşım, severity'nin bir let sabiti olmasına izin vererek değişmezliği korudu, iki aşamalı başlatma kurallarına sıkı sıkıya uydu ve derleyicinin çalışma zamanı yerine derleme zamanında güvenliği doğrulamasını sağladı. Sonuç, açıkça yaş ve şiddet arasındaki veri bağımlılığını ifade eden sıfır çökme ile başlatma dizisiydi ve Swift'in statik analizinden regresyonu önlemek için faydalandı.
Neden derleyici, üst sınıf özellikleriyle ilgisiz gibi görünse de self üzerindeki örnek yöntemlerini çağırmayı engelliyor?
Derleyici, nesnenin ayrılmış bellek olarak var olduğunu, ancak üst sınıf kısmının henüz başlatılmamış ham bellek olduğunu zorunlu kılıyor. Self üzerinde yapılacak herhangi bir yöntem çağrısı - tanımının nerede olduğuna bakılmaksızın - tam nesne işaretçisi alır ve dolaylı yöntemlerle başlatılmamış üst sınıf alanlarına erişebilir, bu da bellek güvenliğini ihlal edebilir. Swift, 1. aşama tamamlanmadan önce self kullanımını temkinli bir şekilde tehlikeli olarak değerlendirir; yalnızca mevcut sınıfın saklanan özelliklerine doğrudan atamalara izin verir.
Kesin başlatma analizi, weak referans özelliklerini unowned referans özelliklerine göre nasıl ele alır?
Kesin başlatma denetleyicisi, weak değişkenleri dahil olmak üzere opsiyonel türleri, derleyici tarafından otomatik olarak enjekte edilen geçerli bir başlangıç değeri olan nil ile sahip kabul eder. Dolayısıyla, weak özelliklerin başlatıcılarda açık başlatma gereksinimi yoktur. Tersine, unowned referanslar opsiyonel değildir ve hemen non-nil anlamını varsayar; bu nedenle, başlatıcı tamamlanmadan önce bir değere atanmalı veya derleyici kesin başlatma hatası verir.
Kesin başlatma ile ilgili olarak kolaylık başlatıcıları ile belirli başlatıcılar için delege kurallarını ayıran nedir?
Kolaylık başlatıcıları, herhangi bir örnek spesifik operasyon gerçekleştirmeden önce bir belirli başlatıcıya (yarı self.init aracılığıyla) delege etmesi gereken ikincil giriş noktalarıdır. Saklanan özellikleri doğrudan başlatma yetkisine kesinlikle sahip değildirler çünkü çağırdıkları belirli başlatıcının kesin başlatma gereksinimlerini yerine getirme sorumluluğu vardır. Bu, yukarıya delege etmeden önce kendi sınıflarına tanıttıkları tüm özellikleri başlatmak zorunda olan belirli başlatıcılarla tezat oluşturur ve her seviyede nesnenin geçerli olmasını sağlar.