Sorunun cevabı.
Swift 5.5'ten önce, eş zamanlılık Grand Central Dispatch (GCD) ve manuel thread yönetimine dayanıyordu; bu durum, sıklıkla paylaşılan korumasız değişken durumdan dolayı veri yarışlarına ve bellek bozulmalarına yol açıyordu. Swift, izolasyon garantileri sağlamak için Actors ile yapılandırılmış eş zamanlılık tanıttı, ancak derleyicinin, bu izole alanlar arasında aktarılan değerlerin doğası gereği thread güvenli olduğunu sağlamak için bir mekanizmaya ihtiyacı vardı. Bu, türleri eşzamanlı sınırlar boyunca paylaşmak için güvenli işaretleyen Sendable protokolüne yol açtı; bu, değer semantiklerini veya tür seviyesinde iç senkronizasyonu zorunlu kılarak sağlandı.
Bir Actor, izolasyon alanından dışarıdan bir değer aldığında, bu değer diğer yürütme bağlamları ile paylaşılan bir referans türü olabileceğinden, bellek güvenliğini ihlal eden eşzamanlı değişikliklere olanak tanıyordu. Geleneksel yaklaşımlar, kritik bölümleri korumak için çalışma zamanı kilitlerine veya mutex'lere dayanıyordu, ancak bunlar ek yük ve deadlock riski getiriyordu ve uygulama sırasında insan hatalarına da açıktı. Zorluk, Swift'in performans özelliklerini ve ergonomisini korurken, derleme zamanında thread güvenliğini statik olarak doğrulayan sıfır maliyetli bir soyutlama tasarlamaktı.
Swift'in derleyicisi, Actor sınırlarını aşan tüm türlerin Sendable uyumluluğunu zorunlu kılarak, çalışma zamanı ek yükü olmadan güvenliği doğrulamak için statik analiz kullanır. Struct ve enum gibi değer türleri, değer semantiklerini sergiledikleri ve paylaşılan değişken durumu önlemek için kopyalama sırasında yazma optimizasyonları kullandıkları için dolaylı olarak Sendable olarak kabul edilir. Referans türleri (class) içinse, derleyici açık bir Sendable uyumluluğu ister, bu da sınıfın final olmasını ve yalnızca Sendable özellikler içermesini zorunlu kılarak, eşzamanlı erişimlerden etkilenemeyen değişmez veya içsel senkronize durumu garantiler.
// Dolaylı olarak Sendable struct struct UserData: Sendable { let id: UUID let score: Int } // Değişmez duruma sahip açıkça Sendable final class final class Configuration: Sendable { let apiEndpoint: String let timeout: Duration init(endpoint: String, timeout: Duration) { self.apiEndpoint = endpoint self.timeout = timeout } } actor DataProcessor { func process(_ data: UserData) async { // Güvenli: UserData Sendable'dır print("İşleniyor \(data.id)") } }
Gerçek zamanlı bir finansal ticaret uygulaması tasarlarken, ekip olarak çoklu WebSocket bağlantılarından piyasa verilerini toplayan bir PriceFeedActor uyguladık, bu, arka planda çalışan bir NetworkManager'dan ayrıştırılmış JSON yüklerini almasını gerektiriyordu. Başlangıçta, yüksek frekanslı güncellemeler sırasında büyük veri setlerini kopyalamaktan kaçınmak için bir referans türü MarketData sınıfı kullandık, ancak Swift derleyicisi, bu nesnelerin Sendable uyumluluğuna sahip olmaması ve hesaplamaları önbelleğe alayan değişken sözlükler içermesi nedeniyle bunları doğrudan Actor'a geçirmemizi engelledi. Bu, Actor'ın izolasyon garantilerini sürdürmek için veri modelimizi yeniden tasarlamak zorunda kalmamıza sebep oldu; aynı zamanda milisaniye altı ticaret kararları için gereken throughput'u da feda etmeksizin.
MarketData'yı büyük bayt tamponları için özel depolama içeren bir struct olarak yeniden yapılandırdık ve ManagedBuffer ile Swift'in kopyalama sırasında yazma mekanizmalarını kullanarak altında yatan depolamayı paylaşmayı sağladık. Bu yaklaşım, derleme zamanı güvenliği sağlarken, okuma ağırlıklı işlemler sırasında bellek çoğalmasını en aza indirgeyerek otomatik olarak dolaylı Sendable uyumluluğu sağladı. Ancak, manuel kopyalama sırasında yazma mantığını uygulamanın karmaşıklığı bakım yükü getirdi ve yoğun yollar üzerindeki yazma işlemleri sırasında otomatik kopyalama davranışı beklenmedik şekilde tetiklendiğinde performans bozulması riski doğurdu.
MarketData referans türünü koruduk ama yalnızca let sabitleri ve derin değişmez Sendable özellikler içeren bir final class olarak yeniden yapılandırdık; bu, birden fazla Actor arasında veri yarışları olmadan tek bir okunabilir örneği paylaşmamıza olanak tanıdı. Bu, büyük veri kümeleri için referans semantiklerinin verimliliğini korudu ve kopyalama yükünü tamamen ortadan kaldırdı, ancak önbellek stratejimizi içsel sınıf değişiklikleri yerine Actor'a izole değişken durum kullanacak şekilde yeniden yapılandırmayı gerektirdi. Mimari değişiklik, değişken durumu belirli Actors'a taşımak için önbellek katmanımızda önemli yeniden yapılandırmalar gerektirdi, kod karmaşıklığını artırmış olsa da, sıkı izolasyon garantileri sağladı.
Hızla yeni Actor modeline geçiş sağlamak için hemen yeniden yapılandırılamayan eski Objective-C köprülenmiş sınıflar için @unchecked Sendable ile işaretledik, böylece derleyici uyarılarını bastırarak içsel kilitler vasıtasıyla thread güvenliğini manuel olarak doğruladık. Bu, yeni Actor modeline hızlı bir geçiş sağladı, ancak etkili olarak Swift'in statik garantilerini devre dışı bıraktı ve manuel senkronizasyon mantığımızda hatalar varsa çalışma zamanı veri yarışları riski getirdi. Sonuç olarak, bu yaklaşımı yalnızca kritik olmayan kayıt alt yapılarına sınırladık; bu, üretim finansal verileri için güvenliğin en kritik olduğu durumlarda kullanılmaktan kaçınılması gerektiği anlamına geliyordu.
Yüksek frekanslı akış verileri için kopyalama sırasında yazma ile optimize edilmiş tasarımlar kullanarak struct yaklaşımını benimsedik; çok sayıda Actor tarafından aynı anda erişilen statik yapılandırma nesneleri içinse değişmez class yaklaşımını sakladık. Bu karma yaklaşım, stres testi sırasında tespit edilen tüm veri yarışı çöküşlerini ortadan kaldırdı ve önceki GCD-temelli mimariye kıyasla eş zamanlılık ile ilgili hata raporlarımızı %94 oranında azalttı. Derleme zamanı Sendable kontrolleri, önceki manuel kilitleme sisteminde üretim çökmelerine neden olacak üç potansiyel yarış durumunu geliştirme sırasında yakaladı.
Sendable'a uyumlu bir tür neden hala async Task'a geçirilen bir closure'a yakalandığında derlenmez ve @Sendable niteliği bu belirsizliği nasıl çözer?
Bir tür Sendable olsa da, Swift'te closure'lar varsayılan olarak değişkenleri referans ile yakalar; bu, closure başka bir Actor'a gönderildikten sonra yakalanan değişkenin sonraki değişimlerine izin verebilir. @Sendable closure niteliği, yakalamaların yalnızca Sendable değerlerle sınırlı olmasını zorunlu kılar ve closure'un kendisinin eş zamanlı alandan güvenli bir şekilde çıkmadığını garanti eder. Bu, closure'un ve yakalanan tüm durumun Actor sınırları boyunca izolasyon garantilerini korumasını sağlar ve asenkron işlemler içindeki değişken yakalama listeleri üzerinden veri yarışlarının ortaya çıkmasını engeller.
Swift 6'nın katı eş zamanlılık kontrolleri, dolaylı olarak içe aktarılan Objective-C başlıklarını nasıl etkiler ve Sendable anotasyonları içermeyen eski çerçeveler ile devam eden etkileşim için hangi mekanizmalar izin verir?
Swift 6, çoğu Objective-C türünü varsayılan olarak non-Sendable olarak işleyen katı eş zamanlılık kontrolleri getirir; çünkü bu türler statik güvenlik garantileri sağlayamaz. Geliştiriciler, güvenlik kontrollerini kademeli olarak benimsemek için @preconcurrency içe aktarma ifadelerini kullanmalı veya manuel olarak Objective-C başlıkları üzerinde SWIFT_SENDABLE makroları ile notasyon yapmalıdır. Bu anotasyonlar, derleyicinin thread güvenliğine sahip eski nesneler ile izolasyon sınırları gerektirenleri ayırt etmesini sağlar, bu da saf Swift kodunun güvenliğini tehlikeye atmadan etkileşim sağlanmasına olanak tanır.
Bir Actor içindeki nonisolated metodlar ile Sendable türleri arasındaki temel fark nedir ve değişken bir sınıf örneği üzerinde nonisolated bir metodu çağırmak ne zaman tanımsız bir davranış ortaya çıkarır?
Nonisolated metodlar, bir Actor'ın verilerine dışındaki izolasyon bağlamından senkronize erişim sağlar, ancak bunlar çağıranın yürütücüsünde değil, Actor'ın seri yürütücüsünde çalıştırılır. Bu, metodun doğrudan değişken Actor durumuna erişmemesi gerektiğini gerektirir; çünkü bu, Actor'ın izolasyon garantilerini atlatabilir. Değişken bir referans türü üzerinde uygulanırsa ve Sendable değilse, nonisolated metodlar, paylaşılan değişken duruma erişip uygun senkronizasyon olmaksızın yarış durumları ortaya çıkarabilir ve bu da bellek bozulmasına veya tanımsız davranışa yol açabilir.