Untuk melakukan debounce pada perhitungan Swift lokal, batalkan tugas tertunda sebelumnya setiap kali input berubah, lalu lakukan perhitungan setelah jeda singkat. Tutorial macOS ini membuat penghitung ubin persegi sintetis yang hanya diperbarui setelah input valid terbaru tidak lagi berubah.
Tentukan dengan tepat apa yang harus dilakukan kalkulator
Tugas bagi pembaca cukup sederhana: tambahkan pratinjau tertunda ke bidang angka di aplikasi Mac. Bayangkan sebuah gambar yang tersusun dari lapisan persegi: lapisan pertama berisi satu ubin, lapisan kedua berisi empat, dan lapisan ketiga berisi sembilan. Memasukkan jumlah lapisan akan menghitung total ubin di seluruh lapisan tersebut. Semuanya tetap berada di memori dan menggunakan angka sintetis.
Untuk contoh ini, ketentuan inputnya adalah:
- Terima bilangan bulat basis 10 dari
0sampai1000, inklusif. - Hapus spasi di awal dan akhir sebelum parsing.
- Tampilkan instruksi segera untuk input kosong atau pesan kesalahan untuk input yang tidak valid.
- Untuk input yang valid, hapus jawaban sebelumnya dan tunggu
300milidetik sebelum menghitung. - Batalkan perhitungan tertunda setiap kali ada perubahan lain atau tampilan menghilang.
Hasilnya adalah jumlah kuadrat dari 1 hingga jumlah lapisan yang diminta. Rumusnya adalah n * (n + 1) * (2 * n + 1) / 6. Untuk 3 lapisan, perhitungan ilustratifnya adalah 1 + 4 + 9 = 14. Untuk 0, hasilnya adalah 0. Ini adalah contoh matematika, bukan hasil aplikasi yang diukur.
Interval 300 milidetik adalah pilihan interaksi sebagai contoh. Ini menghasilkan debounce trailing: setiap perubahan baru memulai ulang periode jeda. Pengeditan terus-menerus dapat menunda perhitungan tanpa batas. Pilih perilaku ini ketika pratinjau setelah input stabil berguna; perintah yang harus merespons setiap perubahan satu per satu memerlukan ketentuan yang berbeda.
Mengapa pembatalan harus menghentikan jalur tertunda
Handle tugas memungkinkan aplikasi membatalkan pekerjaan yang sebelumnya dijadwalkan. Tugas Swift dapat mulai berjalan segera setelah dibuat, dan membuang handle tidak menghentikan tugasnya. Simpan handle agar perubahan berikutnya dapat membatalkannya. Dokumentasi Task dari Apple menjelaskan kedua perilaku tersebut.
Penundaan menggunakan try await Task.sleep(for: .milliseconds(300)). Apple mendokumentasikan bahwa ini menangguhkan tugas tanpa memblokir thread yang mendasarinya dan melempar CancellationError jika dibatalkan sebelum penundaan berakhir. Overload durasi tersedia di macOS 13 dan versi lebih baru. Dokumentasi Task.sleep
Pembatalan tetap bersifat kooperatif: memanggil cancel() tidak menghentikan paksa kode perhitungan sembarang. Jalur tertunda harus berhenti ketika sleep melempar error, dan harus memeriksa pembatalan sekali lagi sebelum menghitung. Dokumentasi Task.cancel, dokumentasi Task.checkCancellation
Kesalahan umum adalah menggunakan try? untuk sleep lalu langsung melanjutkan ke perhitungan. Cara itu membuang error pembatalan yang dilempar dan memungkinkan pernyataan berikutnya dijalankan. Di sini, penanganan error menghentikan tugas sebelum tugas tersebut dapat menghasilkan jawaban yang sudah kedaluwarsa.
Buat contoh macOS lengkap
Buat proyek aplikasi macOS di Xcode dengan antarmuka SwiftUI dan bahasa Swift. Gunakan toolchain Swift 6 dan atur target deployment ke macOS 14 atau versi lebih baru. Contoh ini menggunakan overload onChange(of:initial:_:) modern tanpa argumen, yang menurut daftar Apple tersedia mulai macOS 14. Dokumentasi SwiftUI onChange
Ganti file titik masuk aplikasi yang dihasilkan dengan program lengkap berikut. Hapus file ContentView.swift yang dihasilkan dari target, atau ganti isinya agar hanya ada satu deklarasi ContentView. Pastikan hanya ada satu deklarasi aplikasi bertanda @main di target.
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()
}
}
}
Tampilan mengelola kalkulatornya dengan @StateObject. Apple mendokumentasikan bahwa ini mempertahankan instans model saat input kontainer berubah, sedangkan perubahan identitas tampilan akan membuat instans baru. Properti model yang dipublikasikan menggerakkan status dan jawaban yang ditampilkan. Dokumentasi StateObject
Model dan closure tugas secara eksplisit ditandai dengan @MainActor. Apple mendefinisikan executor aktor ini sebagai setara dengan antrean dispatch utama. Karena itu, dalam implementasi ini, penjadwalan, pembatalan, dan penetapan hasil berlangsung di aktor yang sama. Sleep menangguhkan tugas; ekspresi aritmetika singkat berjalan di aktor tersebut setelah jeda. Dokumentasi MainActor
Bagaimana aturan input dan hasil mencegah pratinjau usang
Pembatalan terjadi sebelum validasi. Jika pengguna mengubah angka valid menjadi bidang kosong atau nilai yang tidak valid, perhitungan tertunda sebelumnya tetap dibatalkan. Keluar lebih awal dari validasi tidak akan membiarkan penundaan lama terus berjalan hingga menghasilkan tampilan yang terlihat.
Program menghapus result pada setiap perubahan. Ini adalah aturan tampilan yang disengaja: jawaban menghilang saat nilai baru menunggu, sehingga antarmuka tidak pernah menyajikan total lama sebagai jawaban untuk teks saat ini. Desain alternatif dapat mempertahankan jawaban dengan label yang jelas bahwa itu hasil sebelumnya, tetapi desain tersebut memerlukan ketentuan tampilan yang berbeda.
trimmingCharacters(in:) dari Foundation menghapus karakter yang cocok dari kedua ujung string. Menggunakan .whitespacesAndNewlines berarti input seperti 3 menjadi 3; karakter di bagian dalam tidak berubah. Dokumentasi pemangkasan Foundation
Parser bilangan bulat Swift secara default menggunakan basis 10, menerima tanda opsional di awal, dan mengembalikan nil untuk nilai yang tidak valid atau tidak dapat direpresentasikan. Karena itu, +3 dan 003 diterima di sini, sedangkan 3.5, 1,000, dan 3 0 ditolak. Nilai negatif hasil parsing ditolak melalui pemeriksaan rentang terpisah. Dokumentasi Int.init
Batas atas juga membatasi aritmetika. Pada 1000 lapisan, pembilangnya adalah 2,003,001,000 dan totalnya 333,833,500. Setiap input yang diizinkan tetap berada dalam batas antara nonnegatif tersebut. Menaikkan batas mengharuskan pemeriksaan perkalian antara selain hasil akhir; membagi dengan enam sesudahnya tidak dapat memperbaiki overflow yang sudah terjadi.
Setiap tugas tertunda menangkap nilai layers yang telah divalidasi untuk perubahan tersebut. Tugas tidak membaca ulang bidang setelah sleep. Setelah tugas lolos pemeriksaan pembatalan, tidak ada lagi await sebelum tugas menetapkan hasil. Pemanggilan penjadwalan lain di main actor tidak dapat menyela segmen perhitungan dan penetapan sinkron tersebut. Panduan bahasa Swift menjelaskan isolasi aktor dan titik suspensi yang ditandai secara eksplisit. Konkurensi — Bahasa Pemrograman Swift
Penalaran ini bergantung pada perhitungan yang tetap sinkron dan sangat singkat. Jika nanti Anda menyisipkan operasi asinkron, periksa kembali pembatalan dan apakah permintaan masih berlaku setelah operasi tersebut selesai. Pengidentifikasi revisi dapat membedakan permintaan yang berurutan, termasuk kasus ketika pengguna kembali ke nilai input sebelumnya.
Periksa perilaku dengan input sintetis
Bangun dan jalankan aplikasi, lalu lakukan urutan penerimaan berikut. Hasil yang diharapkan mengikuti ketentuan program; waktu tepat saat tugas dilanjutkan bergantung pada penjadwalan.
| Tindakan | Perilaku yang diharapkan |
|---|---|
Buka jendela dengan input awal 3 |
Tampilkan status menunggu, lalu Total tiles: 14. |
Ganti input dengan 4 |
Hapus total sebelumnya, lalu tampilkan Total tiles: 30. |
Masukkan 1, lalu 10 sebelum penundaan pertama selesai |
Mulai ulang penundaan; nilai yang sudah stabil menghasilkan Total tiles: 385. |
Masukkan 4, lalu hapus sebelum penundaan selesai |
Tampilkan Enter a layer count. dan tanpa total. |
Masukkan 3.5 atau 1001 |
Segera tampilkan instruksi rentang dan tanpa total. |
Masukkan 0 |
Setelah jeda, tampilkan Total tiles: 0. |
Masukkan 1000 |
Setelah jeda, tampilkan Total tiles: 333833500. |
Untuk pemeriksaan pembatalan, lakukan perubahan berikutnya dengan jeda yang cukup sebelum penundaan berakhir. Pada batas waktunya, hasil lama mungkin sudah selesai sebelum perubahan berikutnya diproses. Perubahan berikutnya tetap menghapus hasil itu dan memulai permintaan baru. Penundaan bukan janji waktu eksekusi yang tepat; panduan bahasa Swift menjelaskan bahwa sleep menangguhkan tugas setidaknya selama interval yang diminta. Dokumentasi konkurensi Swift
Contoh aritmetika di atas diperiksa secara terpisah dengan perhitungan bilangan bulat. Compiler Swift tidak tersedia di lingkungan riset, jadi program macOS tersebut diperiksa tetapi tidak dikompilasi atau dijalankan di sana.
Kaitkan pembatalan dengan masa hidup tampilan
Penangan disappearance memanggil stop() secara eksplisit. Apple mendokumentasikan bahwa onDisappear dijalankan setelah tampilan menghilang, dengan waktu tepat yang bergantung pada jenis tampilan. Perlakukan ini sebagai hook pembersihan tampilan, bukan tenggat pasti untuk peristiwa jendela. Penangan appearance menjadwalkan ulang input saat ini setiap kali tampilan muncul. Dokumentasi SwiftUI onDisappear
Penangan pembatalan sengaja berhenti tanpa mengubah status bersama atau menghapus handle yang tersimpan. Tugas lama yang dibatalkan mungkin dilanjutkan setelah handle baru disimpan; jika tugas lama melakukan pembersihan tanpa syarat, handle baru dapat terhapus. Dalam program ini, metode penjadwalan dan penghentian menghapus handle yang dibatalkan, dan hanya tugas yang berhasil selesai yang menghapus handle aktifnya sendiri.
Debounce mengatur kapan pekerjaan dimulai. Ini tidak membuat perhitungan sinkron yang panjang menjadi responsif, dan pembatalan juga tidak dapat membatalkan hasil yang sudah ditetapkan. Untuk rumus terbatas ini, polanya sederhana: batalkan handle sebelumnya, validasi input yang ditangkap, tangguhkan selama periode jeda, berhenti jika dibatalkan, lalu terapkan perhitungan singkat di main actor. Dengan begitu, antarmuka Mac mendapatkan pratinjau input stabil yang dapat diprediksi.
Sumber
- Task — Apple.
- sleep(for:tolerance:clock:) — Apple.
- cancel() — Apple.
- checkCancellation() — Apple.
- onChange(of:initial:_:) — Apple.
- StateObject — Apple.
- MainActor — Apple.
- trimmingCharacters(in:) — Apple.
- init(_:radix:) — Apple.
- Concurrency — The Swift Programming Language — Swift.org.
- onDisappear(perform:) — Apple.