Swift'in sahiplik modeli, özellikle ~Copyable niteliği ile işaretlenmiş yapılar ve enumlar için kopyalanamaz türler için açık yaşam süresi yönetimini getirir. Bir işlev parametresi borrowing ile işaretlendiğinde, derleyici argümanı işlev çağrısının süresi boyunca paylaşılan, değiştirilemez bir referans olarak ele alır; bu, orijinal bağlamanın geçerliliğini korur ve değerin yaşam süresi dönüşte değişmez. Bu, sahipliği devretmeksizin veya kopyalama işlemlerini tetiklemeksizin birden fazla salt okunur erişime izin verir.
Tam tersine, consuming düzenleyicisi, işlevin değerin sahipliğini aldığını gösterir; bu, çağıran kapsamda yaşam süresini sonlandırarak orijinal bağlamaya sonraki erişimi engeller. Derleyici, bu durumu kesin başlatma analizi ve yalnızca taşınabilir nesnelerin kontrolü ile zorunlu hale getirir; bu, kullanılmadan serbest bırakma hatalarının derleme zamanında değil de çalışma zamanında yakalanmasını sağlar. Bu mekanizma, benzersiz sahipliğin izlenmesi gereken dosya tutacakları veya ağ soketleri gibi kaynakların yönetimi için kritik öneme sahiptir.
Bu düzenleyiciler arasındaki ayrım, Swift'in taşınması yalnızca kaynaklar için bellek güvenliği sağlamasını ve yığın alanında tahsis edilen nesneler için tipik olarak ilişki sayımı yükünü ortadan kaldırmasını sağlar.
struct AudioBuffer: ~Copyable { var data: UnsafeMutablePointer<Float> let frameCount: Int } func analyze(buffer: borrowing AudioBuffer) { // Geçerli: ödünç alınan değerden okuma let firstSample = buffer.data[0] } func process(buffer: consuming AudioBuffer) -> AudioBuffer { // Geçerli: sahipliği devretme ve döndürme buffer.data[0] *= 2.0 return buffer } var buf = AudioBuffer(data: allocateBuffer(), frameCount: 512) analyze(buffer: buf) // buf kullanılmaya devam eder let processed = process(buffer: buf) // buf artık başlatılmamıştır // analyze(buffer: buf) // Hata: buf tüketildikten sonra kullanıldı
Strict olarak 10ms altında gecikme gereksinimlerini karşılamak için birden fazla efekt aşamasından (yankı, sıkıştırma, EQ) geçen büyük çoklu kanal PCM tamponlarını işlemek için gerçek zamanlı bir ses motoru inşa ediyorduk. İlk yaklaşım, ham ses verilerine UnsafeMutablePointer içeren standart kopyalanabilir yapıları kullanıyordu, ancak bu, aşama arası tampon kopyalaması sırasında önemli performans cezaları doğuruyordu. Ayrıca, eğer kopyalanan yapılar, kendi altındaki AudioBuffer havuzlarından daha uzun süre yaşarsa, kaybolan işaretçiler riski taşıdı ve üretimde güvenlik tehlikeleri yarattı.
Düşünülen ilk alternatif, ham tamponları manuel tutma sayıları olan son bir sınıf içinde sarmalayarak referans sayımı kullanan bir sınıf temelli tasarım kullanmaktı. Bu, fiziksel kopyaları ortadan kaldırsa da, atomik referans sayımı yükü ve ses grafı düğümleri arasındaki olası tutma döngüleri ile sonuçlandı; bu durum, gerçek zamanlı iplikler için gerekli belirleyici çözümlemeyi karmaşık hale getirdi ve CPU kullanımını artırdı.
İkinci yaklaşım, doğrudan C fonksiyonları arasında geçiş yapan UnsafeMutablePointer ve Unmanaged referansları ile manuel bellek yönetimini içeriyordu; bu, tamamen Swift güvenliğini atlayarak sıfır yük sundu fakat bellek güvenliğinden ödün verildi, bu da, tamponlar işleme sırasında havuza geri döndüğünde kullanılmadan serbest bırakma hatalarını yakalamak için kapsamlı hata ayıklama gerektiriyordu, bu da geliştirme hızını önemli ölçüde düşürüyordu.
Sonunda, açık sahiplik notları ile kopyalanamaz yapıları benimsedik: tamponları yeni durumlara dönüştüren aşamalar için consuming düzenleyicisi (sahiplik devretme) ve salt okunur analiz aşamaları için borrowing. Bu çözüm, yığın tahsisat yükünü ortadan kaldırırken Swift'in derleme zamanı güvenlik garantilerini korudu ve stres testi sırasında tespit edilen sıfır çalışma zamanı bellek ihlali ile stabil bir 6ms işleme gecikmesine sahip olduk.
borrowing kopyalanamaz türler üzerinden uygulandığında inout ile nasıl farklıdır?**
Her ikisi de alt diziye erişimi sağlasa da, inout münhasır değiştirilebilir erişimi zorunlu kılar ve değerin çağıran tarafına geçerli bir durumda döndürülmesini gerektirir; bu, geçici bir değiştirilemez ödünç alma oluşturur ve bunun, çağıran tarafın yeniden devam etmeden önce sona ermesi gerekir. borrowing, ise paylaşılan salt okunur erişime izin verir ve değerin "dönmesi" veya yeniden başlatılması gerekmediğinden, taşınabilir türler üzerinde değiştirilemez işlemler için uygun hale getirir; münhasır erişim ihlallerini tetiklemeksizin veya çağrılanın değeri yeniden oluşturmasını gerektirmeden.
Bir consuming parametre, işlev gövdesi içinde birden fazla kez kullanılabilir mi?
Evet, ancak kritik kısıtlarla: bir kez tükendiğinde, değer başka bir tüketim bağlamına taşındıktan ya da döndükten sonra bir daha kullanılamaz. Adaylar genellikle consuming'in anında imha anlamına geldiğini varsayıyor, ancak parametre ya başka bir tüketim parametresine taşınıncaya, bir değer olarak döndürüldüğüne ya da kapsam dışına çıkana kadar işlev kapsamı içinde geçerli kalır; bir taşınma işlemi sonrasında erişim denemesi, Swift'in yalnızca taşınabilir kontrolunun tek sahiplik sağladığını sağlamak için bir derleme zamanı hatası ile sonuçlanır.
Neden bir borrowing parametresinin bir örnek özelliğinde saklanması derleyici hatasına neden olur?
borrowing parametreleri, çağıran yığın çerçevesine bağlıdır ve yaşam süreleri, senkron işlev çağrısının süresi ile sıkı bir şekilde sınırlandırılmıştır. Böyle bir referansı bir örnek özelliğine saklamak, yaşam süresini işlev kapsamının ötesine uzatarak, çağıran geri döndüğünde kaybolan bir işaretçi oluşturur ve bellek güvenliğini ihlal eder. Swift, borrowing parametrelerinin işlev çağrısından kaçamadığına dair bir zorunluluk getirerek bunu önler; oysa consuming parametreleri sahipliği devrettiği için yığın üzerinde veya uzatılmış yaşam süreleri ile özellikler olarak saklanabilirler.