SwiftProgramlamaSwift Geliştirici

**defer** bloklarının kapsam çıkışında LIFO yürütme sırasını nasıl garanti ettiğini ve bu davranışın, birden fazla **defer** ifadesinin kontrol akış ifadeleri gibi **throw** veya **return** ile iç içe geçtiği durumlarda neden kaynak güvenliğini sağladığını açıklayın.

Hintsage yapay zeka asistanı ile mülakatları geçin

Soruya cevap

Swift, defer ifadesini her bir sözel kapsamla ilişkilendirilmiş kapatıcı değişkenlerin yığınına dayalı olarak uygulamaktadır. Derleyici bir defer bloğu ile karşılaştığında, kodu bir kapatıcıya çıkarır ve mevcut kapsamın temizlik kaydına kaydeder. Kapsam çıkışı gerçekleştiğinde — normal akış, return, throw veya break aracılığıyla — çalışma zamanı bu kapatıcıları Son-Giren İlk-Çıkar (LIFO) sırasına göre yürütür. Bu yığın disiplini, daha sonra elde edilen kaynakların önce serbest bırakılmasını sağlar, bağımlılık zincirlerini manuel kayıt tutma olmadan korur.

Soru tarihi

Kaynak temizliği tarihsel olarak ya belirleyici yok edicilere ya da ayrıntılı istisna yönetimine dayanmıştır. C++, temizliği nesne ömrü ile RAII yoluyla birleştirirken, Java ve C#, temizlik mantığını edinim kodundan ayıran açık try-finally blokları gerektirir. Go, nesne yönelimli yük olmadan kapsam tabanlı temizliği sağlamak için defer ifadesini tanıtarak Swift’in tasarımını etkilemiştir. Swift 2.0 sürümünde defer’i benimseyerek hata yönetim modelini tamamlamış, finally için temiz bir alternatif sunmuştur ve guard ifadeleri ve erken dönüşlerle sorunsuz bir entegrasyon sağlanmıştır.

Sorun

Kimlik doğrulama, günlüğe kaydetme ve ağ iletimini içeren dosya işlemleri gibi birden fazla çıkış yoluna sahip karmaşık işlevler, titiz kaynak yönetimi gerektirir. Geliştiricilerin, her return veya throw noktasının daha önce edinilen tüm kaynakları serbest bıraktığından emin olmaları gerekir; dosya tanımlayıcılarından güvenlik alanına bağlı yer işaretlerine kadar. Tek bir temizleme noktasının atlanması sızıntıya veya kilitlenmelere neden olurken, yanlış sıralama (bir veritabanasını işlem günlüğünü boşaltmadan kapatmak) veri bozulmasına yol açar. Manuel temizlik, işlev karmaşıklığı arttıkça sürdürülemez hale gelir; bu, kapsam sınırlarına bağlı otomatik, belirleyici ve sıralı kaynak bertarafına ihtiyaç duyar.

Çözüm

Swift derleyicisi, defer ifadelerini, iç kapsamın etkinlik kaydında saklanan bir işlev işaretçileri yığınına dönüştürmektedir. Her defer, yürütme sırasında bu derleyici yönetimindeki yığına kendi kapatıcısını ekler. Kontrol akışı kapsamın kapanış süslü parantezine ulaştığında veya bir çıkış ifadesiyle karşılaştığında, enjekte edilen epilog kodu yığını tersine doğru iterasyona tabi tutar ve her kapatıcıyı yürütür. Bu mekanizma, çalışma zamanının bir hatanın dış catch kapsamına yayılmasından önce tüm bekleyen defer bloklarının çalışmasını garanti ederek temizliğin çıkış yolundan bağımsız olarak gerçekleşmesini sağlar.

Hayattan bir durum

Şifreli kullanıcı verilerini dışa aktaran bir iOS uygulamasını düşünün. Süreç, güvenlik alanına bağlı bir kaynak URL’si edinir, bir FileHandle açar, şifreli baytları yazar ve sonucu yukarı yükler. Her adım başarısız olabilir ve dosya tanımlayıcıları veya kalıcı kaynak yer işaretleri sızdırmamak için katı bir temizliğe ihtiyaç duyar.

Çözüm 1: Her çıkış noktasında manuel temizlik.

Geliştiriciler, her return veya throw öncesinde fileHandle.close() ve url.stopAccessingSecurityScopedResource() ifadelerini çoğaltabilir. Bu yaklaşım kırılgandır; yeni bir hata kontrolü eklemek, birden fazla siteyi güncellemeyi gerektirir ve denetmenlerin temizleme sırasının edinim sırasıyla aynı olduğundan emin olmaları gerekir. Her yeni çıkış noktası eklenirken sızıntı riski artar.

Çözüm 2: deinit ile sarmalayıcı nesneler.

Temizliği deinit içinde gerçekleştiren bir ScopeManager sınıfı yaratmak, ARC’ye dayanır. Ancak, ARC kapsam çıkışında hemen serbest bırakmayı garanti etmez; nesneler, otomatik serbest bırakma havuzu boşalana veya değişken yazılana kadar devam edebilir. Uzun süreli döngülerde, bu kaynak salınımını geciktirir ve yeniden üretmesi zor "çok fazla açık dosya" sistem hatalarına neden olur.

Çözüm 3: defer blokları.

Ekip, her kaynağı edinirken hemen defer blokları ilan etti:

func exportData() throws { let url = try acquireResource() defer { url.stopAccessingSecurityScopedResource() } let fileHandle = try FileHandle(forWritingTo: url) defer { fileHandle.close() } let encrypted = try encrypt(data) try fileHandle.write(encrypted) try upload(fileHandle) }

Bir şifreleme hatası throw tetiklediğinde, çalışma zamanı otomatik olarak dosya tanımlayıcısını kapattı ve kaynağa erişimi durdurdu; böylece doğru ters sırayı korudu. Bu çözüm, belirleyiciliği ve yerelliği için seçildi - temizlik kodu edinim kodunun yanında görünmektedir.

Sonuç:

Dışa aktarma özelliği, dosya tanımlayıcı sızıntısı olmadan 10.000 eşzamanlı işlemle stres testini geçti. Kod incelemesi sıfır atlanmış temizleme yolu ortaya çıkardı ve profil oluşturma, deinit yaklaşımına kıyasla hemen kaynak serbest bırakımı gösterdi.

Adayların genellikle atladığı şeyler

Soru 1: Bir defer bloğu, işlev fatalError ile sona ererse veya sonsuz bir döngüye girerse çalışır mı?

Hayır. defer, kontrol akışı kendi kapsama sonuna ulaştığında yalnızca çalışır. Eğer fatalError çağrılırsa, işlem hemen sona erer, kapsamları açmadan veya temizleme bloklarını çalıştırmadan. Benzer şekilde, sonsuz bir while döngüsü, kapsamın çıkmasını engeller; döngü gövdesindeki defer blokları yalnızca yineleme tamamlandığında çalışır, ancak işlev seviyesinde while true döngüsü asla işlev seviyesindeki defer bloklarını tetiklemez.

Soru 2: defer, değişken defer ilan edildikten sonra değiştirildiğinde değişkeni nasıl işler?

defer, varsayılan olarak referansla değişkenleri yakalar, değerle değil. Örneğin:

var count = 0 defer { print("Deferred: \(count)") } count = 5 // 0 yerine 5 yazdırır

İlan sırasında değeri yakalamak için geliştiricilerin açık bir yakalama listesi kullanması gerekir: defer { [value = currentValue] in ... }. Adaylar genellikle defer’in ilan zamanı itibarıyla bir anlık görüntüyü yakaladığı varsayımına kapılır ve bu, döngülerde veya değişen algoritmalarda mantıksal hatalara yol açar.

Soru 3: defer blokları, koşullu dallar içinde yuvalandığında ve ana kapsamda olduğunda yürütme sırası nedir?

defer blokları, göründükleri sözel kapsam ile ilişkilidir; işlev kapsamıyla değil. Bir if bloğunun içinde bulunan bir defer, o if bloğu çıkarken çalışır, işlev dönerken değil. Eğer farklı yuva seviyelerinde birden fazla defer bloğu varsa, en içteki kapsamın defer’i, belirli bloktan çıkıldığında ilk olarak çalışır. Bu, geliştiricilerin tüm defer bloklarının işlev çıkışında çalışmasını beklediğinde, özellikle guard ifadeleri ile iç içe geçtiğinde, sezgisel olmayan bir sıralama ile sonuçlanır.