Swift, kapamaları C ve Objective-C'ye derleyici tarafından oluşturulan thunk işlevleri ve belirli bellek düzeni dönüşümleri aracılığıyla köprüler. @convention(c) için, derleyici kapamanın boş bir yakalama listesinin olmasını gerektirir çünkü C işlev göstergeleri bağlam parametreleri olmayan ham adreslerdir, bu da dış kapsam değişkenlerine herhangi bir referansın önlenmesi anlamına gelir. @convention(block) için, derleyici yığındaki bir Objective-C blok yapısı oluşturur, isa işaretçisi, bayraklar, çağırma işlevi göstericisi ve yakalanan değişken düzeni ile birlikte, ARC'nin blokun yaşam süresini tutma/serbest bırakma döngüleri aracılığıyla yönetmesine olanak tanır. Kritik invariyant, @convention(c) kapamalarının yığın üzerinde tahsis edilmiş nesnelere referansları yakalamaması gerektiğidir, aksi takdirde asılı işaretçiler oluşur, @convention(block) kapamalarının ise yakalanan referansların Objective-C kodundaki blokun varlığı süresince tutulmasını sağlaması gerekir.
Gerçek zamanlı bir ses işleme kütüphanesi geliştirirken, ekip Core Audio'nun C API'sine (AURenderCallback) geri çağırma işlevleri kaydetmek zorunda kaldı, aynı zamanda tamamlanma işleyicilerini UIKit'nin Objective-C tabanlı animasyon API'lerine de açmalıydı. Ana zorluk, self ve ses tamponu durumunu yakalayan Swift kapamalarını bu yabancı işlev arayüzlerine geçirirken bellek güvenliğini ihlal etmemek ya da tutma döngüleri başlatmaktı. Kısıtlamalar, ses tamponlarına sıfır maliyetle erişim sağlarken, gerçek zamanlı ses iş parçacığı ile ana UI iş parçacığı arasında iş parçacığı güvenliğini korumayı gerektiriyordu.
Düşünülen bir yaklaşım, C geri çağırmaları için global statik işlevlerle bir singleton yöneticisi kullanmaktı. Bu yöntem, bağlamı ses birimi göstergeleriyle anahtar olarak kullanılan bir iş parçacığı yerel sözlüğünde depoladı. Kapatma sorunlarını önlese de, iş parçacığı güvenliği karmaşıklığı ve test edilmesi zor olan global değişken durumu ekledi.
Diğer bir yaklaşım, Swift kapamalarını tutan Objective-C sarmalayıcı sınıfları oluşturmaktı ve bir void* bağlam parametresi aracılığıyla sarmalayıcıyı çözümleyen C işlev işaretçilerini sunmaktı. Durumlu olmasına rağmen, bu köprüleme maliyetini artırdı ve erken serbest bırakmayı önlemek için manuel tutma/serbest bırakma çağrılarını gerektirdi. Manuel bellek yönetimi, sarmalayıcı yaşam döngüsü ses birimi başlatma ve kapama ile mükemmel bir şekilde senkronize edilmediğinde sızıntılara yol açma riski taşıyordu.
Seçilen çözüm, ses motoruna zayıf referanslar içeren bir yapıdan açık bir unsafeBitCast bağlam işaretçisini geçirerek Core Audio geri çağırmaları için @convention(c) kullanmayı, UIKit tamamlama işlevleri için de @convention(block) kullanmayı içeriyordu. Bu, global durumu ortadan kaldırdı ve ARC'nin Objective-C bloklarını doğru bir şekilde yönetmesini sağladı. Ses iş parçacığı geçişleri sırasında C bağlam işaretçilerini korumak için açık bellek engelleri kullanıldı.
Sonuç, belirleyici bellek kullanımı olan sıfır maliyetli bir C köprüsüydü. Sistem, UI katmanında tutma döngüleri sergilemedi ve ses işleme, global kilitler olmadan gerçek zamanlı performans kısıtlamalarını korudu.
Swift, neden dil düzeyinde @convention(c) kapamalarında yakalama yapmayı yasaklar?
C işlev göstergeleri, istemci bilgisi veya "kullanıcı verisi" parametresi desteği olmadan basit bellek adresleri olarak temsil edilir. Bu, herhangi bir kapamanın dış değişkenleri yakalaması durumunda, C kodunun sağlayamayacağı referansları saklamak için bir yere ihtiyaç olduğu anlamına gelir. Swift, geliştiricilerin yığın veya yığın belleği referans veren kapamalar oluşturmasını istemeden önlemek için bu kısıtlamayı derleme zamanında uygular.
ARC, geçerli kapsama alanının ötesinde saklanan Objective-C koduna geçirildiğinde bir @convention(block) kapamasının yaşam döngüsünü nasıl yönetir?
Swift, bir kapamayı @convention(block)'a dönüştürdüğünde, derleyici yığındaki bir Objective-C blok yapısının oluşturulmasını sağlar. Bu yapı, NSObject bellek düzenini izler, bu da ARC'nin blok sınırını aştığında Block_copy ve Block_release işlemlerini uygulamasına olanak tanır. Objective-C kodu, bloğu bir örnek değişkeninde saklarsa, Swift'in ARC entegrasyonu, yakalanan Swift referanslarının tutulmasını garanti eder. Bu referanslar, Objective-C tutucusu bloğu serbest bıraktığında serbest bırakılır, böylece kullanımdan sonra serbest bırakma sorunları önlenirken manuel tutma yönetimi de önlenir.
Bir @convention(c) işlev türünün bellek düzenini standart bir Swift kapama referansından ayıran nedir?
Standart bir Swift kapaması referans sayımlı yığın nesnesidir veya değişkenleri yakalayabilen yığın üzerinde tahsis edilmiş bir bağlam çiftidir. Tersine, bir @convention(c) işlev türü ham bir işlev adresini temsil eden tek bir makine kelimesine derlenir. İlgili hiçbir meta veri, tutma sayıları veya yakalama bağlamı yoktur. Bu ayrım, standart Swift kapamalarının dinamik olarak dağıtılabilmesi ve bellek yönetiminin sağlanabilmesini sağlarken, @convention(c) kapamalarının açık UnsafeMutableRawPointer bağlam parametreleri gerektirmesi anlamına gelir.