Untuk nyahgandakan pengiraan Swift setempat, batalkan tugas tertangguh yang terdahulu setiap kali input berubah, kemudian lakukan pengiraan selepas jeda singkat. Tutorial macOS ini membina pengira jubin segi empat sama sintetik yang hanya dikemas kini selepas input sah yang terkini tidak lagi berubah.

Takrifkan dengan tepat perkara yang perlu dilakukan oleh pengira

Tugas pembaca adalah mudah: tambahkan pratonton tertangguh pada medan nombor dalam app Mac. Bayangkan grafik yang terdiri daripada lapisan segi empat sama: lapisan pertama mengandungi satu jubin, lapisan kedua mengandungi empat, dan lapisan ketiga mengandungi sembilan. Memasukkan bilangan lapisan akan mengira jumlah jubin merentas lapisan tersebut. Semuanya kekal dalam memori dan menggunakan nombor sintetik.

Untuk contoh ini, kontrak input ialah:

  • Terima integer asas 10 daripada 0 hingga 1000, termasuk kedua-duanya.
  • Buang ruang kosong di hadapan dan di belakang sebelum menghuraikan input.
  • Paparkan arahan serta-merta untuk input kosong atau ralat untuk input tidak sah.
  • Untuk input yang sah, kosongkan jawapan sebelumnya dan tunggu 300 milisaat sebelum mengira.
  • Batalkan pengiraan yang belum selesai apabila suntingan lain diterima atau paparan hilang.

Hasilnya ialah jumlah kuasa dua daripada 1 hingga bilangan lapisan yang diminta. Formulanya ialah n * (n + 1) * (2 * n + 1) / 6. Untuk 3 lapisan, contoh pengiraannya ialah 1 + 4 + 9 = 14. Untuk 0, hasilnya ialah 0. Ini ialah contoh matematik, bukannya hasil aplikasi yang diukur.

Selang 300 milisaat ialah pilihan interaksi sebagai contoh. Ia menghasilkan nyahgandaan hujung: setiap suntingan baharu memulakan semula tempoh senyap. Penyuntingan berterusan boleh menangguhkan pengiraan tanpa had. Pilih tingkah laku ini apabila pratonton input yang sudah mantap berguna; arahan yang mesti bertindak balas terhadap setiap suntingan individu memerlukan kontrak yang berbeza.

Sebab pembatalan mesti menghentikan laluan tertangguh

Pemegang tugas membolehkan app membatalkan kerja yang dijadualkan sebelum ini. Tugas Swift boleh mula berjalan sebaik sahaja dicipta, dan membuang pemegang tidak menghentikan tugasnya. Simpan pemegang itu supaya suntingan seterusnya boleh membatalkannya. Dokumentasi Task Apple menerangkan kedua-dua tingkah laku ini.

Kelewatan menggunakan try await Task.sleep(for: .milliseconds(300)). Apple mendokumenkan bahawa kaedah ini menggantung tugas tanpa menyekat benang asasnya dan melontarkan CancellationError jika dibatalkan sebelum kelewatan tamat. Overload tempoh tersedia pada macOS 13 dan versi lebih baharu. Dokumentasi Task.sleep

Pembatalan kekal bersifat kerjasama: memanggil cancel() tidak mengganggu kod pengiraan sewenang-wenangnya secara paksa. Laluan tertangguh mesti kembali apabila sleep melontarkan ralat, dan perlu menyemak pembatalan sekali lagi sebelum mengira. Dokumentasi Task.cancel, Dokumentasi Task.checkCancellation

Kesilapan lazim ialah menggunakan try? untuk sleep, kemudian terus melakukan pengiraan. Tindakan itu membuang ralat pembatalan yang dilontarkan dan membenarkan pernyataan seterusnya dijalankan. Di sini, pengendalian ralat akan keluar daripada tugas sebelum tugas itu menghasilkan jawapan lapuk.

Bina contoh macOS yang lengkap

Cipta projek app macOS dalam Xcode menggunakan antara muka SwiftUI dan bahasa Swift. Gunakan rantai alat Swift 6 dan tetapkan sasaran pelaksanaan kepada macOS 14 atau versi lebih baharu. Contoh ini menggunakan overload onChange(of:initial:_:) moden tanpa argumen, yang menurut senarai Apple tersedia mulai macOS 14. Dokumentasi SwiftUI onChange

Gantikan fail titik masuk app yang dijana dengan program lengkap di bawah. Alih keluar fail ContentView.swift yang dijana daripada sasaran, atau gantikan kandungannya supaya hanya terdapat satu pengisytiharan ContentView. Pastikan hanya satu pengisytiharan app yang ditandai dengan @main dalam sasaran.

import SwiftUI
import Combine
import Foundation

@MainActor
final class TileCalculator: ObservableObject {
    @Published private(set) var result: Int?
    @Published private(set) var status = "Enter a layer count."

    private var pending: Task<Void, Never>?

    func schedule(_ text: String) {
        pending?.cancel()
        pending = nil
        result = nil

        let cleaned = text.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !cleaned.isEmpty else {
            status = "Enter a layer count."
            return
        }
        guard let layers = Int(cleaned, radix: 10),
              (0...1000).contains(layers) else {
            status = "Use a whole number from 0 to 1000."
            return
        }

        status = "Waiting for typing to pause..."
        pending = Task { @MainActor [weak self] in
            do {
                try await Task.sleep(for: .milliseconds(300))
                try Task.checkCancellation()
            } catch {
                return
            }

            guard let self else { return }
            let total = layers * (layers + 1) * (2 * layers + 1) / 6
            self.result = total
            self.status = "Calculated for \(layers) layers."
            self.pending = nil
        }
    }

    func stop() {
        pending?.cancel()
        pending = nil
        result = nil
        status = "Calculation paused."
    }
}

@MainActor
struct ContentView: View {
    @State private var input = "3"
    @StateObject private var calculator = TileCalculator()

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Square-layer tile counter")
                .font(.headline)
            TextField("Layers: 0 to 1000", text: $input)
                .textFieldStyle(.roundedBorder)
            Text(calculator.status)
            if let total = calculator.result {
                Text("Total tiles: \(total)")
                    .font(.title2)
            }
        }
        .padding(24)
        .frame(minWidth: 360, minHeight: 180)
        .onAppear {
            calculator.schedule(input)
        }
        .onChange(of: input) {
            calculator.schedule(input)
        }
        .onDisappear {
            calculator.stop()
        }
    }
}

@main
@MainActor
struct LocalTileCalculatorApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Paparan ini memiliki pengiranya menggunakan @StateObject. Apple mendokumenkan bahawa ini mengekalkan tika model merentas perubahan pada input bekas, manakala perubahan pada identiti paparan akan mencipta tika baharu. Sifat model yang diterbitkan memacu status dan jawapan yang dipaparkan. Dokumentasi StateObject

Model dan penutupan tugas ditandai dengan @MainActor secara eksplisit. Apple mentakrifkan pelaksana aktor ini sebagai setara dengan baris gilir penghantaran utama. Dalam pelaksanaan ini, penjadualan, pembatalan dan penetapan hasil berkongsi aktor yang sama. Sleep menggantung tugas; ungkapan aritmetik ringkas berjalan pada aktor itu selepas tempoh menunggu. Dokumentasi MainActor

Cara peraturan input dan hasil menghalang pratonton lapuk

Pembatalan berlaku sebelum pengesahan. Jika pengguna menukar nombor yang sah kepada medan kosong atau nilai tidak sah, pengiraan tertangguh yang lama tetap dibatalkan. Keluar awal daripada pengesahan tidak akan membiarkan kelewatan lama itu terus berjalan sehingga menghasilkan keputusan yang kelihatan.

Program mengosongkan result pada setiap suntingan. Ini ialah peraturan paparan yang disengajakan: jawapan hilang sementara nilai baharu menunggu, jadi antara muka tidak akan memaparkan jumlah lama sebagai jawapan kepada teks semasa. Reka bentuk alternatif boleh mengekalkan jawapan dengan label yang jelas menandakan hasil sebelumnya, tetapi itu memerlukan kontrak paparan yang berbeza.

trimmingCharacters(in:) daripada Foundation membuang aksara yang sepadan pada kedua-dua hujung rentetan. Penggunaan .whitespacesAndNewlines bermakna input seperti 3 menjadi 3; aksara di bahagian dalam tidak berubah. Dokumentasi pemangkasan Foundation

Penghuraian integer Swift menggunakan asas 10 secara lalai, menerima tanda pilihan di hadapan dan mengembalikan nil untuk nilai yang tidak sah atau tidak dapat diwakili. Oleh itu, +3 dan 003 diterima di sini, manakala 3.5, 1,000 dan 3 0 ditolak. Nilai negatif yang berjaya dihuraikan ditolak melalui semakan julat yang berasingan. Dokumentasi Int.init

Had atas juga mengehadkan aritmetik. Pada 1000 lapisan, pengangkanya ialah 2,003,001,000 dan jumlahnya ialah 333,833,500. Setiap input yang dibenarkan kekal dalam had perantaraan bukan negatif tersebut. Menaikkan had memerlukan semakan pendaraban perantaraan serta hasil akhir; membahagikan dengan enam selepas itu tidak dapat membetulkan limpahan yang sudah berlaku.

Setiap tugas tertangguh menangkap nilai layers yang telah disahkan untuk suntingan tersebut. Tugas itu tidak membaca semula medan selepas tidur. Setelah tugas melepasi semakan pembatalannya, tiada lagi await sebelum tugas itu menetapkan hasil. Panggilan penjadualan aktor utama yang lain tidak boleh mencelah segmen pengiraan dan penetapan yang segerak itu. Panduan bahasa Swift menerangkan pengasingan aktor dan titik penggantungan yang ditandai secara eksplisit. Concurrency — Bahasa Pengaturcaraan Swift

Penaakulan ini bergantung pada pengiraan yang kekal segerak dan sangat ringkas. Jika anda kemudian menambah operasi tak segerak, semak semula pembatalan dan sama ada permintaan itu masih yang terkini selepas operasi tersebut kembali. Pengecam revisi boleh membezakan permintaan berturutan, termasuk keadaan apabila pengguna kembali kepada nilai input yang terdahulu.

Semak tingkah laku dengan input sintetik

Bina dan jalankan app, kemudian ikuti urutan penerimaan ini. Hasil yang dijangka berpandukan kontrak program; masa sebenar tugas disambung semula bergantung pada penjadualan.

Tindakan Tingkah laku yang dijangka
Buka tetingkap dengan input awal 3 Paparkan status menunggu, kemudian Total jubin: 14.
Gantikan input dengan 4 Kosongkan jumlah sebelumnya, kemudian paparkan Total jubin: 30.
Masukkan 1, kemudian 10 sebelum kelewatan pertama selesai Mulakan semula kelewatan; nilai yang sudah mantap menghasilkan Total jubin: 385.
Masukkan 4, kemudian padamkannya sebelum kelewatan selesai Paparkan Masukkan bilangan lapisan. dan tiada jumlah.
Masukkan 3.5 atau 1001 Paparkan arahan julat serta-merta dan tiada jumlah.
Masukkan 0 Selepas kelewatan, paparkan Total jubin: 0.
Masukkan 1000 Selepas kelewatan, paparkan Total jubin: 333833500.

Untuk menyemak pembatalan, buat suntingan seterusnya dengan cukup awal sebelum kelewatan tamat. Pada sempadan masa itu, hasil lama mungkin sudah selesai sebelum suntingan seterusnya diproses. Suntingan seterusnya tetap mengosongkannya dan memulakan permintaan baharu. Kelewatan bukan janji bahawa tugas akan dilaksanakan pada masa yang tepat; panduan bahasa Swift menerangkan bahawa sleep menggantung tugas sekurang-kurangnya selama selang yang diminta. Dokumentasi keserentakan Swift

Contoh aritmetik di atas disemak secara berasingan menggunakan pengiraan integer. Pengkompil Swift tidak tersedia dalam persekitaran penyelidikan, jadi program macOS itu diperiksa tetapi tidak dikompil atau dijalankan di sana.

Kekalkan pembatalan sejajar dengan jangka hayat paparan

Pengendali kehilangan paparan memanggil stop() secara eksplisit. Apple mendokumenkan bahawa onDisappear dijalankan selepas paparan hilang, dengan masa tepat bergantung pada jenis paparan. Anggap ia sebagai cangkuk pembersihan paparan, bukannya tarikh akhir tepat bagi peristiwa tetingkap. Pengendali kemunculan menjadualkan semula input semasa setiap kali paparan muncul. Dokumentasi SwiftUI onDisappear

Pengendalian ralat pembatalan sengaja kembali tanpa mengubah status kongsi atau mengosongkan pemegang yang disimpan. Tugas lama yang dibatalkan mungkin disambung semula selepas pemegang baharu disimpan; jika tugas lama itu melakukan pembersihan tanpa syarat, ia boleh memadamkan pemegang baharu. Dalam program ini, kaedah penjadualan dan penghentian mengosongkan pemegang yang dibatalkan, dan hanya tugas yang berjaya selesai akan mengosongkan pemegang aktifnya sendiri.

Nyahgandaan mengawal masa kerja bermula. Ia tidak menjadikan pengiraan segerak yang panjang lebih responsif, dan pembatalan tidak boleh membatalkan hasil yang sudah ditetapkan. Untuk formula terhad ini, corak yang berguna adalah ringkas: batalkan pemegang terdahulu, sahkan input yang ditangkap, gantung tugas sepanjang tempoh senyap, keluar jika dibatalkan, kemudian tetapkan pengiraan ringkas pada aktor utama. Ini memberikan pratonton input yang sudah mantap dan boleh dijangka kepada antara muka Mac.

Sumber