Bir Swift modelini actor’a taşırken atomik karşılaştır ve değiştir işlemini korumak için karşılaştırmayı ve atamayı tek bir eşzamanlı, actor yalıtımlı yöntemin içinde tutun. Çağıranlar await kullanır, ancak yöntem kontrol ile değiştirme arasında askıya alınmamalıdır.
Bu anlatım, daha önce kilitle korunan küçük bir masaüstü modelini taşıyan bir Mac geliştiricisine yöneliktir. Somut görev, sentetik bir şekil etiketini yalnızca mevcut değeri beklenen değerle eşleşiyorsa değiştirmektir. Örnek tamamen bellekte çalışır; ağ istekleri, harici kayıtlar veya kalıcılık kullanmaz.
Modeli değiştirmeden önce değer sözleşmesini tanımlayın
Bu örnekte compareAndReplace(expected:replacement:) üç kurala sahiptir: mevcut etiketi expected ile karşılaştırın; eşleşme varsa replacement değerini atayıp true döndürün; aksi durumda etiketi değiştirmeden false döndürün. Karşılaştırma Swift’in değer eşitliğini kullanır. Standart kitaplığın Equatable belgeleri, bu karşılaştırmayı == aracılığıyla tanımlar ve değer eşitliğini sınıf örneği kimliğinden ayırır.
Karşılaştırma, işlem yürütüldüğü sıradaki güncel değeri kullanmalıdır. Daha önce alınmış bir anlık görüntü beklenen değeri sağlayabilir, ancak değiştirmenin daha sonra başarılı olması gerektiğini belirleyemez. Bu karar, değişikliği gerçekleştiren işlemin içinde verilmelidir.
| Mevcut değer | Beklenen değer | Değiştirilecek değer | Sonuç | Sonraki değer |
|---|---|---|---|---|
"Circle" |
"Circle" |
"Square" |
true |
"Square" |
"Square" |
"Circle" |
"Triangle" |
false |
"Square" |
"Square" |
"Square" |
"Square" |
true |
"Square" |
Son satır kasıtlıdır: başarı, karşılaştırmanın eşleştiği ve atamanın kabul edildiği anlamına gelir. Görünen değerin değiştiği anlamına gelmez. Taşıma sırasında bu dönüş kuralını değiştirmek sözleşmeyi değiştirir.
Kilit tabanlı bir uygulamada karşılaştırmanın ve atamanın tamamı aynı korumalı kritik bölümde yer almalıdır. Apple, Foundation’ın NSLock’unu kritik bir bölümü atomik olarak çalıştırmak üzere korumanın bir yolu olarak belgeler. Actor’a geçerken bu işlem sınırını koruyun: parçalarını çağıranlara dağıtmak yerine kararın tamamını actor’a taşıyın.
Burada “atomik”, bu actor üzerindeki başka bir işlemin karşılaştırma ile atama arasına giremeyeceği anlamına gelir. Bu örnek, donanımın atomik yönergeleri veya kilitsiz yürütme hakkında bir iddiada bulunmaz.
Çağıranın await kullanması işlemi neden bölmez?
Eşzamanlı bir actor yöntemi, yalıtım alanının dışından çağrıldığında await gerektirebilir. Actor’a girmek için beklerken çağıranın askıya alınması gerekebilir. Yöntem yürütülmeye başladığında, olağan eşzamanlı gövdesinde başka bir görevin girip aynı actor durumunu değiştirebileceği bir askıya alınma noktası bulunmaz. Swift bu ayrımı SE-0306: Actors içinde belgeler.
Dil kılavuzu, olası askıya alınma noktaları içermedikleri için güncellemelerin eşzamanlı actor yöntemlerinde kapsüllenmesini önerir. Apple’ın actor oturumu da eşzamanlı actor kodunun, başka bir görev actor yalıtımlı yürütmesini kesintiye uğratmadan tamamlandığını açıklar. Bu uygulamanın dayandığı garantiler bunlardır. Bkz. Concurrency ve Protect mutable state with Swift actors.
Dolayısıyla uygun sınır, koşulu ve bundan doğan değişikliği birlikte içeren tek bir actor çağrısıdır. Çağıran önce bir anlık görüntü okuyabilir, yerel bir hesaplama yapabilir ve ardından koşullu değiştirme isteği gönderebilir. Actor, bu isteği işlerken koşulu yeniden denetlemelidir.
Ayrı okuma ve yazma işlemlerinde ne yanlış gider?
Taşıma işleminin ayrı bir anlık görüntü ve koşulsuz değiştirme yöntemi sunduğunu varsayalım. Bir çağıran "Circle" değerini okur. Başka bir çağıran etiketi "Square" olarak değiştirir. İlk çağıran daha sonra önceki okumasına dayanarak "Triangle" yazar. Her bir yöntem actor yalıtımına uyabilir, ancak genel işlem dizisi amaçlanan karşılaştır ve değiştir sözleşmesini ihlal eder.
Bu açıklayıcı zamanlama, belirli bir görev zamanlayıcısına bağlı olmadan kusuru gösterir. Actor yalıtımı her işlemi korur; uygulamanın koşulunu koruyacak kadar kapsamlı bir işlem seçmek yine programcının sorumluluğundadır.
Mac’inizde eksiksiz bir yerel örnek oluşturun
Yüklü bir Swift 6 araç zinciri kullanın. Swift’in resmi macOS kurulum kılavuzu, Swiftly üzerinden kurulumu ve Xcode ile geliştirmeyi açıklar. Devam etmeden önce Terminal’de seçili derleyiciyi kontrol edin:
swiftc --version
Aşağıdaki eksiksiz programı çalışma klasörünüzde CompareReplace.swift adıyla kaydedin. Yalnızca açık bir içe aktarma gerektirmeden kullanılabilen Swift standart kitaplığını kullanır. Dosya actor’ı, başlangıç durumunu, tüm örnek girdileri ve eşzamansız bir giriş noktasını tanımlar.
actor ShapeLabelModel {
private var label: String
init(initial: String) {
label = initial
}
func snapshot() -> String {
label
}
func compareAndReplace(
expected: String,
replacement: String
) -> Bool {
guard label == expected else {
return false
}
label = replacement
return true
}
}
@main
struct CompareReplaceDemo {
static func main() async {
let model = ShapeLabelModel(initial: "Circle")
let matched = await model.compareAndReplace(
expected: "Circle", replacement: "Square"
)
let afterMatch = await model.snapshot()
precondition(matched && afterMatch == "Square")
let stale = await model.compareAndReplace(
expected: "Circle", replacement: "Triangle"
)
let afterStale = await model.snapshot()
precondition(!stale && afterStale == "Square")
let unchanged = await model.compareAndReplace(
expected: "Square", replacement: "Square"
)
precondition(unchanged)
let contested = ShapeLabelModel(initial: "Circle")
async let first = contested.compareAndReplace(
expected: "Circle", replacement: "Square"
)
async let second = contested.compareAndReplace(
expected: "Circle", replacement: "Triangle"
)
let (firstWon, secondWon) = await (first, second)
let finalLabel = await contested.snapshot()
precondition(firstWon != secondWon)
precondition(
finalLabel == (firstWon ? "Square" : "Triangle")
)
let cycle = ShapeLabelModel(initial: "Circle")
let oldSnapshot = await cycle.snapshot()
let movedAway = await cycle.compareAndReplace(
expected: "Circle", replacement: "Square"
)
let movedBack = await cycle.compareAndReplace(
expected: "Square", replacement: "Circle"
)
let matchedAgain = await cycle.compareAndReplace(
expected: oldSnapshot, replacement: "Triangle"
)
let afterCycle = await cycle.snapshot()
precondition(movedAway && movedBack && matchedAgain)
precondition(afterCycle == "Triangle")
print("Matching replacement: \(matched)")
print("Stale replacement: \(stale)")
print("Equal replacement: \(unchanged)")
print("Exactly one competing replacement succeeded.")
print("A restored value matches the old snapshot.")
}
}
Programı bu klasörden derleyip çalıştırın. -parse-as-library seçeneği açık @main giriş noktasını desteklerken -swift-version 6, Swift 6 dil modunu seçer.
swiftc -swift-version 6 -parse-as-library CompareReplace.swift -o compare-demo
./compare-demo
Programdaki denetimlerden çıkarılan beklenen çıktı şöyledir:
Matching replacement: true
Stale replacement: false
Equal replacement: true
Exactly one competing replacement succeeded.
A restored value matches the old snapshot.
Kod belgelerle karşılaştırılarak incelendi, ancak bu ortamda Swift derleyicisi bulunmadığından çalıştırılmadı. Dahil edilen önkoşullar, programı çalıştırdığınızda yerel denetimler sağlar; bunlar programın çalıştırıldığı iddiası değildir.
Yarışan çağrılar neyi gösteriyor?
İki async let bildirimi, ilk sonucu bekleyip sonra ikincisini başlatmak yerine alt görevler oluşturur. Yapılandırılmış eşzamanlılığın bu kullanımı, dil kılavuzunun eşzamanlı eşzamansız çağrılar açıklamasına uygundur. Her iki çağıran da "Circle" değerini bekler ve her biri farklı bir değiştirme değeri önerir.
Bu özel kurulumda yalnızca biri başarılı olur: hangisi önce yürütülürse değeri "Circle" değerinden uzaklaştırır; diğerinin karşılaştırması başarısız olur. Test, kazananın hangisi olabileceğini kısıtlamaz. SE-0306 içinde belgelendiği üzere Swift, actor’ı bekleyen görevlerin başlangıçta bekleme sırasına göre yürütüleceğini garanti etmez.
Örnek ayrıca await edilen çağrılarla precondition ifadelerini birbirinden ayırır. Önce her sonucu bir yerel sabite kaydeder, sonra bu sabiti denetler. Böylece eşzamansız işler eşzamanlı doğrulama ifadelerinin dışında tutulur.
Son karşılaştırmadan önce eşzamansız hazırlığı tamamlayın
Gelecekteki bir sürüm, başka sentetik yerel işleri kullanarak önerilen etiketi eşzamansız biçimde hesaplayabilir. Eşzamanlı karşılaştır ve değiştir yöntemini çağırmadan önce bu hazırlığı tamamlayın. Özgün beklenen değeri ve hesaplanan değiştirme değerini birlikte iletin; false sonucunu reddedilmiş bir girişim olarak ele alın.
Eşitliği denetleyen, hazırlık için await kullanan ve ardından koşulsuz atama yapan bir actor yönteminde bir boşluk oluşur. Yöntem askıdayken başka bir görev actor’ın durumunu değiştirebilir. Devam eden yöntem artık geçerli olmayabilecek bir koşula dayanıyor olur. Swift’in belgelenmiş actor yeniden giriş modeli bu araya girmeye açıkça izin verir.
Hazırlık eşzamansız bir actor yönteminde gerçekleşiyorsa, hazırlık tamamlandıktan sonra eşzamanlı karşılaştırma yöntemini çağırın. Bu son yöntem yeni bir karşılaştırma yapmalıdır. Askıya alınmadan önce yapılan bir denetim gereksiz işi önlemeye yardımcı olabilir, ancak nihai atamaya yetki veremez.
Eşleşme yoksa masaüstü arayüzünün göstermesi gerekiyorsa önerilen sonucu saklayın, yeni bir anlık görüntü alın ve yeniden hesaplama ya da çakışmayı bildirme seçeneklerinden birini belirleyin. Beklenen değeri otomatik olarak değiştirip yeniden denemek, isteği “gözlemlediğim değeri değiştir”den “daha yeni değerleri temel alarak denemeyi sürdür”e dönüştürür. Bu davranışı açık bir ürün kararı hâline getirin.
Eski Foundation kilidini bir await boyunca tutmayın. Apple, NSLock’un kilidi alan iş parçacığında açılmasını şart koşar. Burada gösterilen actor tasarımı, eşzamansız iş boyunca kilit tutmadan, kısa ve eşzamanlı, yalıtılmış bir işlemle gereken sonucu sağlar.
Eşitlik anlambilimini koruyun ve sınırlarını bilin
Döngü denetimi incelikli bir sınırı gösterir. Çağıran "Circle" anlık görüntüsünü alır; model "Square" olur, ardından yeniden "Circle" değerine döner. Sözleşme değişiklikler dizisini değil mevcut değeri karşılaştırdığı için eski anlık görüntüyle yapılan karşılaştırma başarılı olur.
Gerçek gereksiniminiz “anlık görüntümden bu yana kabul edilmiş hiçbir değişiklik olmadıysa değiştir” ise yalnızca değer karşılaştırması yeterli değildir. Desteklenen bir tasarım, hem değeri hem de bir revizyon belirtecini içeren anlık görüntü döndürmek, ardından belirteci denetleyip değeri tek bir eşzamanlı actor yönteminin içinde güncellemektir. Bu, daha güçlü ve farklı bir sözleşmedir. Uygulamadan önce belirtecin nasıl ilerleyeceğini ve tükendiğinde ne olacağını tanımlayın; değerleri koruyan bir taşıma sırasında bunu sessizce devreye sokmayın.
Daha büyük modeller için eşitliği, değiştirme açısından önemli tüm alanları kapsayan bir anlık görüntü değeri kullanın. Equatable başvurusuna göre, tüm saklanan özellikleri uyumluysa Swift bir yapı için Equatable uyumunu sentezleyebilir. İlgili bir alanı yok sayan eşitlik uygulaması, o alandaki değişikliklere rağmen değiştirmelere izin verir.
Actor sınırını geçen değerleri eşzamanlı kullanıma uygun tutun. Sendable belgeleri, Sendable değerlerin eşzamanlılık alanları arasında aktarılabildiğini ve Sendable yapıların Sendable üyeler içermesi gerektiğini açıklar. Rastgele değiştirilebilir bir başvuruyu bir yapıya sarmak bu özelliği sağlamaz.
Bu yerel model için taşıma; actor değiştirilebilir etikete sahip olduğunda, her koşullu değiştirme birleşik işlem üzerinden yapıldığında ve çağıranlar bu işlemin Boolean sonucunu özgün sözleşmeye göre yorumladığında tamamlanmış olur. Son karşılaştırma ile atamayı birlikte tutun; reddedilen bir girişimin ne anlama geldiğine çevresindeki masaüstü iş akışı karar versin.
Kaynaklar
- NSLock — Apple Geliştirici Belgeleri.
- Equatable — Apple Geliştirici Belgeleri.
- Sendable — Apple Geliştirici Belgeleri.
- Concurrency — Swift Programlama Dili, Swift projesi.
- Actors — SE-0306 — Swift Evolution, Swift projesi.
- Protect mutable state with Swift actors — Apple Developer, WWDC21.
- Install Swift — macOS — Swift.org.