Değiştirilebilir tek bir yineleyici oluşturun ve art arda gelen her değer için await iterator.next() çağrısını yapın. Küçük bir Mac komut satırı hesabı için akışı ve yineleyiciyi yinelenen okuma işleminin dışında tutun ve nil değerini tamamlanma olarak kabul edin.
Bu anlatımda 4, 7 ve 10 sentetik tam sayıları tüketilir, her birinin karesi alınır ve toplamın 165 olduğu doğrulanır. Hesaplama yalnızca bellekte oluşturulan değerleri kullanır. Amacı, yineleyicinin ömrünü, arabelleğe almayı ve tamamlanmayı tek bir küçük programda görünür kılmaktır.
Değerler tekrarlandığında gerçekte ne yeniden başlar?
Swift, asenkron diziyi yineleyicisinden ayırır. Dizi, makeAsyncIterator() aracılığıyla bir yineleyici sağlar; yineleyici ise next() çağrılarıyla ilerler. Her çağrı bir öğe döndürür veya yineleme sona erdiğinde nil döndürür. Apple bu sözleşmeyi AsyncIteratorProtocol içinde belgeler.
Bir uygulama ilk değeri tekrar tekrar aldığında incelenmesi gereken iki farklı işlem vardır: başka bir yineleyici oluşturmak ve başka bir akış oluşturmak. Bunlar birbirinin yerine kullanılamaz. Mevcut standart kitaplık uygulamasında bir AsyncStream yineleyicisi akışın paylaşılan bağlamını tutar ve makeAsyncIterator() bu mevcut bağlamı yineleyiciye aktarır. Bu nedenle aynı akışta başka bir yineleyici oluşturmak, baştan bağımsız bir yeniden oynatma başlatmaz. Bkz. resmî AsyncStream uygulaması.
Buna karşılık, yeni bir akış oluşturan bir fabrika işlevini çağırıp 4, 7 ve 10 değerlerini üretmek, bu değerleri içeren başka bir kaynak oluşturur. Her okuma bu fabrika işlevini çağırıp yalnızca bir öğe tüketirse her okuma 4 değerini alabilir. Alıcısında akış oluşturan hesaplanmış bir özellik de aynı etkiye yol açabilir. Bu görev için kaynağı bir kez oluşturun ve hesaplamanın tamamında tek bir yineleyici tutun.
Kodu nereye taşıyacağınıza karar verirken bu ayrımı kullanın: kaynağı korumak için akış oluşturmayı yinelenen işlemin dışına, tek ve anlaşılır bir tüketim yolunu korumak için de yineleyici oluşturmayı bu işlemin dışına taşıyın. İlerlemeyi sürdürmek veya yeniden başlatmak için yineleyiciyi tekrar tekrar oluşturmaya güvenmeyin.
Mac’inizde tamamlanmış yerel bir hesaplama çalıştırın
Swift 6 veya daha yeni bir araç zincirine sahip bir Mac kullanın. Terminal uygulamasında yüklü derleyiciyi kontrol edin, ardından yeni bir dizinde küçük bir çalıştırılabilir paket oluşturun. Swift.org’un Komut Satırı Aracı Oluşturma kılavuzu sürüm kontrolü, paket oluşturma ve swift run iş akışını açıklar.
swift --version
mkdir IteratorDemo
cd IteratorDemo
swift package init --name IteratorDemo --type executable
Oluşturulan çalıştırılabilir kaynak dosyasını Sources dizininde bulun. İçeriğini aşağıdaki programın tamamıyla değiştirin. Kaynak dosyasına IteratorDemo.swift adını verin; şablon main.swift oluşturduysa dosyayı yeniden adlandırın ve özgün içeriğini kaldırın. Yalnızca tek bir çalıştırılabilir giriş noktası bırakın. Swift.org, aynı çalıştırılabilir dosyada main.swift giriş noktası ile @main giriş noktasının bir arada kullanılamayacağını belgeler.
Bu örnek için içe aktarma gerekmez: AsyncStream Swift’e aittir; Foundation ise ayrı bir çerçevedir. Hesaplama hiçbir Foundation API’sine ihtiyaç duymaz. Bu ayrımlar Apple’ın AsyncStream belgelerinde ve Foundation genel bakışında yer alır.
@main
struct IteratorDemo {
static func makeValues() -> AsyncStream<Int> {
AsyncStream<Int>(bufferingPolicy: .bufferingOldest(3)) {
continuation in
for value in [4, 7, 10] {
let result = continuation.yield(value)
guard case .enqueued = result else {
preconditionFailure("A sample value was not enqueued")
}
}
continuation.finish()
}
}
static func main() async {
let stream = makeValues()
var iterator = stream.makeAsyncIterator()
var received: [Int] = []
var squareTotal = 0
for readNumber in 1...3 {
guard let value = await iterator.next() else {
preconditionFailure("Stream ended before read \(readNumber)")
}
received.append(value)
squareTotal += value * value
print("Read \(readNumber): \(value)")
}
let end = await iterator.next()
let stillEnded = await iterator.next()
precondition(received == [4, 7, 10])
precondition(squareTotal == 165)
precondition(end == nil)
precondition(stillEnded == nil)
print("Square total: \(squareTotal)")
print("Finished: \(end == nil)")
}
}
Çalıştırılabilir dosyayı paket dizininden çalıştırın:
swift run IteratorDemo
Derleme iletilerinden sonra uygulamadan beklenen çıktı şöyledir:
Read 1: 4
Read 2: 7
Read 3: 10
Square total: 165
Finished: true
Aritmetik özellikle örnek amacıyla verilmiştir: 4 × 4 + 7 × 7 + 10 × 10 = 165. Doğrulamalar kesin değerleri, sıralarını, hesaplamayı ve sonlandırma davranışını denetler. Araştırma ortamında Swift derleyicisi bulunmadığından bu program burada incelendi, ancak çalıştırılmadı.
Bu yineleyici neden doğru şekilde ilerliyor?
Program makeValues() öğesini bir kez, makeAsyncIterator() öğesini de bir kez çağırır. Üç okumanın tamamı aynı değiştirilebilir değişkeni kullanır. Apple, yineleyicinin next() yöntemini mutasyon yapan ve asenkron bir yöntem olarak tanımlar; bu da hem var iterator kullanımını hem de await iterator.next() ifadesini açıklar.
Üretici, tüketim başlamadan önce akış oluşturulurken üç değerini de üretir. Bu nedenle arabellek kapasitesi örneğin sözleşmesinin bir parçasıdır: .bufferingOldest(3) üç tam sayının tamamına yer sağlar. Her yield sonucunu denetlemek, hesaplamanın eksik değerlerle sürmesini önler ve beklenmeyen bir kuyruğa ekleme hatasını görünür kılar. Apple, hiçbir okuma beklemediğinde yield(_:) yönteminin bir değeri arabelleğe almaya çalıştığını belgeler.
Bu üretimlerden sonra finish() çağrısı, arabelleğe alınmış değerlerin tüketilmesine izin verirken üretimi kapatır. Kabul edilen Swift önerisi, arabelleğe alınmış öğelerin sonlandırıcı nil öğesinden önce tüketiciye ulaştığını açıkça belirtir. Bu nedenle üretici zaten tamamlanmış olsa bile ilk üç okuma başarılı olur. Bkz. AsyncStream ve AsyncThrowingStream, SE-0314.
Dördüncü okuma, tamamlanmış ve tükenmiş akışta nil değerini döndürür. Beşinci okuma sonlandırma sözleşmesini doğrular: bir yineleyici nil döndürdükten sonra sonraki okumalar da nil döndürmelidir. nil değerini daha sonra yeniden deneme veya yineleyiciyi yeniden oluşturma talimatı olarak yorumlamayın; AsyncIteratorProtocol bunu yinelemenin sonu olarak tanımlar.
Açık okumalar mı, tek ve kesintisiz döngü mü?
Numaralandırılmış hesaplamada olduğu gibi, çevresindeki işlemin tek tek okumalar arasında denetim yapması gerektiğinde açık next() çağrılarını kullanın. Yineleyiciyi, tüm tüketim oturumunun sahibi olan kapsamda tutun. Her çağrıda kendi yineleyicisini oluşturan bir yardımcı işlev bu sahipliği belirsizleştirir; akışı da yeniden oluşturan bir yardımcı işlev ise kaynağın kendisini değiştirir.
Görev yalnızca tamamlanana kadar her öğeyi işlemekse tek bir for await döngüsü kullanın. Apple’ın AsyncStream genel bakışı bu desteklenen tüketim biçimini gösterir. Örnek programda aşağıdaki parça, mevcut main() yönteminin tamamının yerine kullanılmalıdır; mevcut makeValues() yöntemini ve çevresindeki türü koruyun:
static func main() async {
let stream = makeValues()
var received: [Int] = []
var squareTotal = 0
for await value in stream {
received.append(value)
squareTotal += value * value
}
precondition(received == [4, 7, 10])
precondition(squareTotal == 165)
print("Square total: \(squareTotal)")
}
Bu sürüm, tek ve kesintisiz bir tüketim oturumunu ifade eder. Ayrı okuma komutları gerekmediğinde bunu seçin. Tek tek okumalar görevin yapısının parçasıysa açık yineleyiciyi seçin. Her iki yaklaşım da doğru oluşturulmuş bir kaynağa ve tanımlanmış bir sona ihtiyaç duyar.
Tekrarlanan, eksik veya tamamlanmayan okumaları tanılayın
Aşağıdaki sıra, kaynak ömrü sorunlarını arabelleğe alma ve tamamlanma sorunlarından ayırır. Yeni davranışlar eklemeden önce bunu sabit örneğe uygulayın.
- Tekrarlanan
4: kaynak oluşturmayı inceleyin. Her işleminmakeValues()çağrısını yeniden yapıp yapmadığını veya yeni bir akış oluşturan bir alıcıya erişip erişmediğini kontrol edin. Fabrika işlevi çağrısını yinelenen okumaların dışına taşıyın. - Tüketiciler arasında beklenmeyen değerler: sahipliği inceleyin. Bu hesaplamaya bir tüketici ve bir yineleyici verin. Aynı akış üzerindeki birden fazla yineleyici, değerlerin birbirinden bağımsız kopyalarını sağlamaz.
- Eksik değerler: arabellek kapasitesini inceleyin. Örnek, tüketim başlamadan önce üç tam sayının tamamını üretir. Daha küçük bir arabellek, hangi değerlerin kullanılabilir kaldığını değiştirir.
- Tamamlanmayan son okuma: tamamlanmayı inceleyin. Continuation tabanlı bir akışta boş arabellek tek başına üretimi bitirmez. Sonlu üreticinin
finish()çağrısını yaptığından emin olun. - Çakışan okumalar: tüketimi sıraya koyun. Yineleyicide başka bir okumaya başlamadan önce bir okumayı await edin.
Bu örneğin koşulları altında arabellek değişikliklerinin sonuçları öngörülebilir. .bufferingOldest(2) ile ilk iki değer kalır ve yeni 10 değeri atılır. .bufferingNewest(2) ile arabellekteki en eski 4 değeri atılır ve geriye 7 ile 10 kalır. Bunlar, Apple’ın belgelenmiş bufferingOldest(_:) ve bufferingNewest(_:) ilkelerinden, belirtilen üretim sırası için çıkarılan sonuçlardır.
Bu ilkelerin hiçbiri üreticinin tüketimi beklemesini sağlamaz. Apple, yield(_:) çağrısının hemen sonuçlandığını belgeler; dolayısıyla tek bir yineleyiciyi korumak, yetersiz arabellek kapasitesini telafi edemez. Bu üç öğeli sonlu hesaplama için üçlük kapasite yeterlidir; farklı bir üretim düzeni kendi kapasite kararını gerektirir.
Yineleyiciye erişimi sıralı tutun. Apple, AsyncStream.Iterator türünün Sendable protokolüne uymadığını ve next() için eşzamanlı olarak yarışan çağrıların programcı hatası olduğunu belirtir. İptal de continuation’ı etkiler: tüketici görevi bir değer beklerken iptal edilirse Apple’ın next() belgelerine göre akış sonlanır. Başka bir yineleyici oluşturmak, iptal edilmiş akışı sürdürmenin belgelenmiş bir yolu değildir.
Bu yerel hesaplama için tüketim sözleşmesi basittir: tek bir kaynak oluşturun, değiştirilebilir tek bir yineleyiciyi saklayın, okumaları sırayla await edin, üç değerin tamamını arabellekte tutun ve nil noktasında durun. Bu seçimler ilerlemeyi açıkça belirtir ve son toplamın doğrulanabilir bir anlam taşımasını sağlar.
Kaynaklar
- AsyncStream — Apple.
- AsyncIteratorProtocol — Apple.
- AsyncStream.Iterator — Apple.
- next() — Apple.
- yield(_:) — Apple.
- AsyncStream.Continuation.BufferingPolicy.bufferingOldest(_:) — Apple.
- AsyncStream.Continuation.BufferingPolicy.bufferingNewest(_:) — Apple.
- Foundation — Apple.
- Komut Satırı Aracı Oluşturma — Swift.org.
- AsyncStream.swift — Swift projesi.
- AsyncStream ve AsyncThrowingStream, SE-0314 — Swift projesi.