Cipta satu iterator boleh ubah dan panggil await iterator.next() untuk setiap nilai seterusnya. Untuk pengiraan baris perintah Mac yang ringkas, kekalkan kedua-dua strim dan iterator di luar operasi bacaan berulang, dan anggap nil sebagai penamat.
Panduan ini menggunakan integer sintetik 4, 7, dan 10, mengkuasa duakan setiap nilai, dan menyemak bahawa jumlahnya ialah 165. Pengiraan ini hanya menggunakan nilai yang dicipta dalam memori. Tujuannya adalah untuk memperlihatkan jangka hayat iterator, penimbalan dan penamat dalam satu program kecil.
Apakah yang sebenarnya dimulakan semula apabila nilai berulang?
Swift memisahkan jujukan tak segerak daripada iteratornya. Jujukan menyediakan iterator melalui makeAsyncIterator(), dan iterator bergerak ke hadapan melalui panggilan kepada next(). Setiap panggilan memulangkan elemen atau nil apabila iterasi telah tamat. Apple mendokumenkan kontrak ini dalam AsyncIteratorProtocol.
Terdapat dua operasi berbeza yang perlu diperiksa apabila aplikasi berulang kali menerima nilai pertama: membina iterator lain dan membina strim lain. Kedua-duanya tidak boleh saling menggantikan. Dalam pelaksanaan pustaka standard semasa, iterator AsyncStream memegang konteks kongsi strim, dan makeAsyncIterator() menghantar konteks sedia ada itu kepada iterator. Oleh itu, membina iterator lain pada strim yang sama tidak menghasilkan main semula bebas dari awal. Lihat pelaksanaan AsyncStream rasmi.
Sebaliknya, memanggil kilang yang mencipta strim baharu dan menghasilkan 4, 7, dan 10 akan mencipta sumber lain yang mengandungi nilai-nilai tersebut. Jika setiap bacaan memanggil kilang itu dan menggunakan hanya satu elemen, setiap bacaan boleh menerima 4. Sifat terhitung yang membina strim dalam getter-nya boleh memberikan kesan yang sama. Untuk tugas ini, bina sumber sekali dan kekalkan satu iterator sepanjang keseluruhan pengiraan.
Gunakan perbezaan ini apabila menentukan tempat untuk memindahkan kod: pindahkan penciptaan strim ke luar operasi berulang untuk mengekalkan sumber, dan pindahkan penciptaan iterator ke luar operasi itu untuk mengekalkan satu laluan penggunaan yang jelas. Jangan bergantung pada penciptaan iterator berulang sebagai mekanisme untuk memulakan semula atau mengekalkan kemajuan.
Jalankan pengiraan setempat yang lengkap pada Mac anda
Gunakan Mac dengan rantaian alat Swift 6 atau lebih baharu. Dalam Terminal, semak pengkompil yang dipasang, kemudian cipta pakej boleh laksana kecil dalam direktori baharu. Panduan Bina Alat Baris Perintah Swift.org mendokumenkan semakan versi, penciptaan pakej dan aliran kerja swift run.
swift --version
mkdir IteratorDemo
cd IteratorDemo
swift package init --name IteratorDemo --type executable
Cari sumber boleh laksana yang dijana di bawah Sources. Gantikan kandungannya dengan program lengkap di bawah. Namakan fail sumber itu IteratorDemo.swift; jika templat mencipta main.swift, namakan semula fail itu dan buang kandungan asalnya. Kekalkan hanya satu titik masuk boleh laksana. Swift.org mendokumenkan bahawa titik masuk main.swift dan titik masuk @main tidak boleh digabungkan dalam boleh laksana yang sama.
Tiada import diperlukan untuk contoh ini: AsyncStream ialah sebahagian daripada Swift, manakala Foundation ialah rangka kerja berasingan. Pengiraan ini tidak memerlukan API Foundation. Sempadan ini diterangkan dalam dokumentasi AsyncStream Apple dan gambaran keseluruhan Foundation.
@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)")
}
}
Jalankan boleh laksana itu dari direktori pakej:
swift run IteratorDemo
Output aplikasi yang dijangka, selepas sebarang mesej binaan, ialah:
Read 1: 4
Read 2: 7
Read 3: 10
Square total: 165
Finished: true
Aritmetik ini jelas bersifat ilustrasi: 4 × 4 + 7 × 7 + 10 × 10 = 165. Pernyataan pengesahan menyemak nilai tepat, susunannya, pengiraan dan tingkah laku terminal. Pengkompil Swift tidak tersedia dalam persekitaran penyelidikan, jadi program ini telah diperiksa tetapi tidak dijalankan di sini.
Mengapakah iterator ini terus bergerak dengan betul?
Program ini memanggil makeValues() sekali dan makeAsyncIterator() sekali. Ketiga-tiga bacaannya menggunakan pemboleh ubah boleh ubah yang sama. Apple mengisytiharkan kaedah next() iterator sebagai mutating dan tak segerak, yang menerangkan kedua-dua var iterator dan await iterator.next().
Pengeluar menghasilkan tiga nilainya semasa pembinaan strim, sebelum penggunaan bermula. Oleh itu, kapasiti penimbal ialah sebahagian daripada kontrak contoh ini: .bufferingOldest(3) mempunyai ruang untuk ketiga-tiga integer. Menyemak hasil setiap yield menjadikan kegagalan enku masuk yang tidak dijangka dapat dilihat, dan mengelakkan pengiraan diteruskan dengan nilai yang hilang. Apple mendokumenkan bahawa yield(_:) cuba menimbal nilai apabila tiada bacaan sedang menunggu.
Memanggil finish() selepas nilai-nilai itu dihasilkan akan menutup penghasilan sambil membolehkan nilai yang telah ditimbal digunakan. Cadangan Swift yang diterima menyatakan dengan jelas bahawa elemen tertimbal sampai kepada pengguna sebelum nil terminal. Itulah sebabnya tiga bacaan pertama berjaya walaupun pengeluar telah selesai. Lihat AsyncStream dan AsyncThrowingStream, SE-0314.
Bacaan keempat mendapati strim yang telah selesai dan kehabisan elemen lalu memulangkan nil. Bacaan kelima mengesahkan kontrak terminal: selepas iterator memulangkan nil, bacaan seterusnya juga mesti memulangkan nil. Jangan tafsirkan nil sebagai arahan untuk mencuba semula kemudian atau membina semula iterator; AsyncIteratorProtocol mentakrifkannya sebagai penghujung iterasi.
Pilih bacaan eksplisit atau satu gelung berterusan
Gunakan panggilan next() yang eksplisit apabila operasi yang melingkunginya memerlukan kawalan antara bacaan individu, seperti dalam pengiraan bernombor ini. Kekalkan iterator dalam skop yang memiliki keseluruhan sesi penggunaan. Pembantu yang mencipta iterator sendiri pada setiap panggilan mengaburkan pemilikan itu; pembantu yang turut membina semula strim akan mengubah sumber itu sendiri.
Apabila tugasnya sekadar memproses setiap elemen sehingga selesai, gunakan satu gelung for await. Gambaran keseluruhan AsyncStream Apple menunjukkan bentuk penggunaan yang disokong ini. Untuk program contoh, berikut ialah fragmen pengganti bagi keseluruhan kaedah main() sedia ada; kekalkan kaedah makeValues() sedia ada dan jenis yang mengelilinginya:
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)")
}
Versi ini menggambarkan satu sesi penggunaan berterusan. Pilihnya apabila tiada arahan bacaan berasingan diperlukan. Pilih iterator eksplisit apabila bacaan individu merupakan sebahagian daripada struktur tugas. Kedua-dua pendekatan tetap memerlukan sumber yang dibina dengan betul dan penamat yang ditentukan.
Diagnosis bacaan berulang, hilang atau tidak selesai
Urutan berikut membezakan masalah jangka hayat sumber daripada masalah penimbalan dan penamat. Gunakannya pada contoh tetap sebelum menambah tingkah laku lain.
4berulang: periksa pembinaan sumber. Semak sama ada setiap operasi memanggilmakeValues()sekali lagi, atau mengakses getter yang mencipta strim baharu. Letakkan panggilan kilang di luar bacaan berulang.- Nilai yang tidak dijangka merentas pengguna: periksa pemilikan. Berikan pengiraan ini seorang pengguna dan satu iterator. Beberapa iterator pada strim yang sama tidak menyediakan salinan nilainya secara berasingan.
- Nilai hilang: periksa kapasiti penimbal. Contoh ini menghasilkan ketiga-tiga integer sebelum penggunaan. Penimbal yang lebih kecil mengubah nilai yang kekal tersedia.
- Bacaan akhir yang tidak selesai: periksa penamat. Dalam strim berasaskan continuation, penimbal kosong sahaja tidak menamatkan penghasilan. Pastikan pengeluar terhingga memanggil
finish(). - Bacaan bertindih: urutkan penggunaan. Tunggu satu bacaan selesai sebelum memulakan bacaan lain pada iterator.
Perubahan penimbal membawa kesan yang boleh dijangka di bawah syarat contoh ini. Dengan .bufferingOldest(2), dua nilai pertama kekal dan nilai baharu 10 digugurkan. Dengan .bufferingNewest(2), nilai tertua yang ditimbal, iaitu 4, dibuang, lalu meninggalkan 7 dan 10. Ini ialah kesimpulan berdasarkan susunan penghasilan yang dinyatakan, mengikut dasar Apple yang didokumenkan bufferingOldest(_:) dan bufferingNewest(_:).
Kedua-dua dasar tidak membuat pengeluar menunggu penggunaan. Apple mendokumenkan bahawa yield(_:) memulangkan hasil serta-merta, jadi mengekalkan satu iterator tidak dapat mengimbangi penimbal yang terlalu kecil. Untuk pengiraan terhingga tiga elemen ini, kapasiti tiga mencukupi; corak penghasilan yang berbeza memerlukan keputusan kapasiti tersendiri.
Kekalkan akses iterator secara berjujukan. Apple menyatakan bahawa AsyncStream.Iterator tidak mematuhi Sendable dan panggilan serentak yang bersaing kepada next() merupakan kesilapan pengaturcara. Pembatalan juga mempengaruhi continuation: jika tugas pengguna dibatalkan ketika menunggu nilai, dokumentasi next() Apple menyatakan bahawa strim akan ditamatkan. Mencipta iterator lain bukan cara yang didokumenkan untuk menyambung semula strim yang dibatalkan itu.
Untuk pengiraan setempat ini, kontrak penggunaan lengkapnya ringkas: cipta satu sumber, kekalkan satu iterator boleh ubah, tunggu bacaan secara berjujukan, kekalkan ketiga-tiga nilai dalam penimbal dan berhenti pada nil. Pilihan tersebut menjadikan kemajuan jelas dan memberikan makna yang boleh disemak kepada jumlah akhir.
Sumber
- AsyncStream — Apple.
- AsyncIteratorProtocol — Apple.
- AsyncStream.Iterator — Apple.
- next() — Apple.
- yield(_:) — Apple.
- AsyncStream.Continuation.BufferingPolicy.bufferingOldest(_:) — Apple.
- AsyncStream.Continuation.BufferingPolicy.bufferingNewest(_:) — Apple.
- Foundation — Apple.
- Bina Alat Baris Perintah — Swift.org.
- AsyncStream.swift — Projek Swift.
- AsyncStream dan AsyncThrowingStream, SE-0314 — Projek Swift.