स्थानीय Swift गणना को डीबाउंस करने के लिए, इनपुट बदलने पर पिछले विलंबित टास्क को रद्द करें, फिर थोड़ी देर रुकने के बाद गणना करें। macOS का यह ट्यूटोरियल एक कृत्रिम वर्गाकार टाइल काउंटर बनाता है, जो केवल नवीनतम मान्य इनपुट स्थिर होने के बाद अपडेट होता है।
तय करें कि कैलकुलेटर को ठीक-ठीक क्या करना चाहिए
पाठक का काम सीधा है: Mac ऐप के नंबर फ़ील्ड में विलंबित पूर्वावलोकन जोड़ें। वर्गाकार परतों से बने ग्राफ़िक की कल्पना करें: पहली परत में एक टाइल है, दूसरी में चार और तीसरी में नौ। परतों की संख्या दर्ज करने पर उन परतों की कुल टाइलों की गणना होती है। सब कुछ मेमोरी में रहता है और कृत्रिम संख्याओं का उपयोग करता है।
इस उदाहरण के लिए, इनपुट का अनुबंध यह है:
0से1000तक, दोनों सीमाओं सहित, आधार-10 पूर्णांक स्वीकार करें।- पार्स करने से पहले आगे और पीछे की खाली जगह हटाएँ।
- खाली इनपुट पर तुरंत निर्देश दिखाएँ या अमान्य इनपुट पर त्रुटि दिखाएँ।
- मान्य इनपुट के लिए, पिछला उत्तर हटाएँ और गणना करने से पहले
300मिलीसेकंड प्रतीक्षा करें। - हर नया बदलाव आने पर या दृश्य के गायब होने पर लंबित गणना रद्द करें।
परिणाम 1 से अनुरोधित परतों की संख्या तक के वर्गों का योग है। सूत्र n * (n + 1) * (2 * n + 1) / 6 है। 3 परतों के लिए उदाहरणात्मक गणना 1 + 4 + 9 = 14 है। 0 के लिए परिणाम 0 है। ये गणितीय उदाहरण हैं, ऐप्लिकेशन के मापे गए परिणाम नहीं।
300 मिलीसेकंड का अंतराल इंटरैक्शन का एक उदाहरण है। इससे ट्रेलिंग डीबाउंस बनता है: हर नया बदलाव शांत अवधि को फिर से शुरू करता है। लगातार बदलाव करते रहने से गणना अनिश्चित समय तक टल सकती है। जब स्थिर इनपुट का पूर्वावलोकन उपयोगी हो, तब यह व्यवहार चुनें; हर अलग-अलग बदलाव पर प्रतिक्रिया देना आवश्यक हो, तो उसके लिए अलग अनुबंध चाहिए।
विलंबित पथ को रद्द करना क्यों ज़रूरी है
टास्क हैंडल ऐप को पहले से शेड्यूल किए गए काम को रद्द करने देता है। Swift टास्क बनते ही चलना शुरू कर सकते हैं, और हैंडल छोड़ देने से टास्क नहीं रुकता। हैंडल सहेजकर रखें, ताकि अगला बदलाव उसे रद्द कर सके। Apple का Task दस्तावेज़ीकरण इन दोनों व्यवहारों का वर्णन करता है।
विलंब के लिए try await Task.sleep(for: .milliseconds(300)) का उपयोग किया गया है। Apple दस्तावेज़ करता है कि यह टास्क को उसके अंतर्निहित थ्रेड को ब्लॉक किए बिना निलंबित करता है और विलंब पूरा होने से पहले रद्द किए जाने पर CancellationError फेंकता है। अवधि वाला ओवरलोड macOS 13 और उसके बाद के संस्करणों में उपलब्ध है। Task.sleep का दस्तावेज़ीकरण
रद्दीकरण सहयोगात्मक रहता है: cancel() को कॉल करने से मनमाना गणना कोड बलपूर्वक बाधित नहीं होता। विलंबित पथ को sleep से त्रुटि आने पर लौटना चाहिए और गणना करने से पहले रद्दीकरण की फिर जाँच करनी चाहिए। Task.cancel का दस्तावेज़ीकरण, Task.checkCancellation का दस्तावेज़ीकरण
एक आम गलती यह है कि sleep के लिए try? का उपयोग करके सीधे गणना जारी रखी जाए। इससे फेंकी गई रद्दीकरण त्रुटि छिप जाती है और आगे के स्टेटमेंट चल सकते हैं। यहाँ, त्रुटि को पकड़ने पर टास्क गणना से पहले ही बाहर निकल जाता है, इसलिए पुराना उत्तर नहीं दिखता।
पूरा macOS उदाहरण बनाएँ
Xcode में SwiftUI इंटरफ़ेस और Swift भाषा का उपयोग करके macOS ऐप प्रोजेक्ट बनाएँ। Swift 6 टूलचेन का उपयोग करें और डिप्लॉयमेंट टार्गेट macOS 14 या बाद का संस्करण सेट करें। इस उदाहरण में आधुनिक शून्य-आर्ग्युमेंट वाला onChange(of:initial:_:) ओवरलोड उपयोग किया गया है, जिसे Apple macOS 14 से उपलब्ध बताता है। SwiftUI onChange का दस्तावेज़ीकरण
बनाई गई ऐप एंट्री-पॉइंट फ़ाइल को नीचे दिए गए पूरे प्रोग्राम से बदलें। जनरेट की गई ContentView.swift फ़ाइल को टार्गेट से हटाएँ या उसकी सामग्री बदलें, ताकि ContentView की केवल एक घोषणा रहे। टार्गेट में @main से चिह्नित ऐप की ठीक एक घोषणा रखें।
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()
}
}
}
दृश्य अपने कैलकुलेटर का स्वामित्व @StateObject से रखता है। Apple दस्तावेज़ करता है कि इससे कंटेनर के इनपुट बदलने पर भी मॉडल इंस्टेंस बना रहता है, जबकि दृश्य की पहचान बदलने पर नया इंस्टेंस बनता है। प्रकाशित मॉडल प्रॉपर्टियाँ दिखाए गए स्टेटस और उत्तर को संचालित करती हैं। StateObject का दस्तावेज़ीकरण
मॉडल और टास्क क्लोज़र को स्पष्ट रूप से @MainActor चिह्नित किया गया है। Apple इस ऐक्टर के एक्ज़ीक्यूटर को मुख्य डिस्पैच क्यू के समकक्ष परिभाषित करता है। इसलिए, इस कार्यान्वयन में शेड्यूलिंग, रद्दीकरण और परिणाम असाइन करना एक ही ऐक्टर पर होते हैं। sleep टास्क को निलंबित करता है; प्रतीक्षा के बाद छोटा अंकगणितीय व्यंजक उसी ऐक्टर पर चलता है। MainActor का दस्तावेज़ीकरण
इनपुट और परिणाम के नियम पुराने पूर्वावलोकन से कैसे बचाते हैं
सत्यापन से पहले रद्दीकरण होता है। यदि उपयोगकर्ता कोई मान्य संख्या बदलकर फ़ील्ड खाली कर देता है या अमान्य मान दर्ज करता है, तो पहले से लंबित गणना फिर भी रद्द हो जाती है। सत्यापन से जल्दी लौटने पर पुराना विलंब चलता नहीं रह सकता और न ही वह कोई परिणाम दिखा सकता है।
प्रोग्राम हर बदलाव पर result साफ़ करता है। यह डिस्प्ले का जानबूझकर चुना गया नियम है: नए मान की प्रतीक्षा के दौरान उत्तर गायब हो जाता है, इसलिए इंटरफ़ेस कभी भी पुराने कुल को मौजूदा टेक्स्ट का उत्तर बनाकर नहीं दिखाता। एक वैकल्पिक डिज़ाइन में पिछले परिणाम को स्पष्ट लेबल के साथ रखा जा सकता है, लेकिन उसके लिए डिस्प्ले का अलग अनुबंध चाहिए।
Foundation का trimmingCharacters(in:) स्ट्रिंग के दोनों सिरों से मेल खाते वर्ण हटाता है। .whitespacesAndNewlines का उपयोग करने पर 3 जैसा इनपुट 3 बन जाता है; बीच के वर्ण वैसे ही रहते हैं। Foundation में ट्रिमिंग का दस्तावेज़ीकरण
Swift का पूर्णांक पार्सर डिफ़ॉल्ट रूप से आधार 10 का उपयोग करता है, शुरुआत में वैकल्पिक चिह्न स्वीकार करता है और अमान्य या दर्शाने में असमर्थ मानों के लिए nil लौटाता है। इसलिए, यहाँ +3 और 003 स्वीकार किए जाते हैं, जबकि 3.5, 1,000 और 3 0 अस्वीकार किए जाते हैं। पार्स किया गया ऋणात्मक मान अलग रेंज जाँच में अस्वीकार होता है। Int.init का दस्तावेज़ीकरण
ऊपरी सीमा अंकगणित को भी सीमित रखती है। 1000 परतों पर अंश 2,003,001,000 है और कुल 333,833,500 है। हर अनुमत इनपुट इन गैर-ऋणात्मक मध्यवर्ती सीमाओं के भीतर रहता है। सीमा बढ़ाने पर अंतिम परिणाम के साथ-साथ मध्यवर्ती गुणन की भी जाँच करनी होगी; बाद में छह से भाग देने पर पहले हो चुके ओवरफ़्लो को ठीक नहीं किया जा सकता।
हर विलंबित टास्क उस बदलाव के लिए सत्यापित layers मान को कैप्चर करता है। sleep के बाद वह फ़ील्ड को दोबारा नहीं पढ़ता। टास्क की रद्दीकरण जाँच सफल होने के बाद, परिणाम असाइन करने से पहले कोई और await नहीं होता। मुख्य ऐक्टर पर शेड्यूलिंग की कोई दूसरी कॉल उस समकालिक गणना और असाइनमेंट वाले हिस्से के बीच नहीं आ सकती। Swift की भाषा गाइड ऐक्टर आइसोलेशन और स्पष्ट रूप से चिह्नित सस्पेंशन पॉइंट्स समझाती है। Concurrency — The Swift Programming Language
यह तर्क गणना को समकालिक और बहुत छोटा रखने पर निर्भर करता है। यदि बाद में कोई एसिंक्रोनस ऑपरेशन जोड़ें, तो उसके लौटने के बाद रद्दीकरण और यह जाँच फिर करें कि अनुरोध अब भी मौजूदा है या नहीं। रिविज़न आइडेंटिफ़ायर लगातार आने वाले अनुरोधों में अंतर कर सकता है, उन स्थितियों में भी जब उपयोगकर्ता पहले वाले इनपुट मान पर लौटता है।
कृत्रिम इनपुट से व्यवहार जाँचें
ऐप बनाएँ और चलाएँ, फिर नीचे दिए गए स्वीकृति क्रम का उपयोग करें। अपेक्षित परिणाम प्रोग्राम के अनुबंध से निकलते हैं; जागने का सटीक समय शेड्यूलिंग पर निर्भर करता है।
| कार्रवाई | अपेक्षित व्यवहार |
|---|---|
प्रारंभिक इनपुट 3 के साथ विंडो खोलें |
प्रतीक्षा वाला स्टेटस दिखाएँ, फिर कुल टाइलें: 14 दिखाएँ। |
इनपुट को 4 से बदलें |
पिछला कुल हटाएँ, फिर कुल टाइलें: 30 दिखाएँ। |
1 दर्ज करें, फिर पहला विलंब पूरा होने से पहले 10 दर्ज करें |
विलंब फिर से शुरू करें; स्थिर हुआ मान कुल टाइलें: 385 देता है। |
4 दर्ज करें, फिर विलंब पूरा होने से पहले उसे मिटा दें |
परतों की संख्या दर्ज करें। दिखाएँ और कोई कुल न दिखाएँ। |
3.5 या 1001 दर्ज करें |
रेंज का निर्देश तुरंत दिखाएँ और कोई कुल न दिखाएँ। |
0 दर्ज करें |
विलंब के बाद कुल टाइलें: 0 दिखाएँ। |
1000 दर्ज करें |
विलंब के बाद कुल टाइलें: 333833500 दिखाएँ। |
रद्दीकरण की जाँच के लिए, अगला बदलाव विलंब पूरा होने से काफ़ी पहले करें। सीमा के ठीक आसपास, अगला बदलाव संसाधित होने से पहले पुराना परिणाम पूरा हो चुका हो सकता है। अगला बदलाव फिर भी उसे साफ़ करके नया अनुरोध शुरू करता है। विलंब ठीक किसी तय समय पर निष्पादन की नियुक्ति नहीं है; Swift की भाषा गाइड बताती है कि sleep टास्क को अनुरोधित अंतराल से कम से कम उतनी अवधि तक निलंबित करता है। Swift समवर्तीता का दस्तावेज़ीकरण
ऊपर दिए गए अंकगणितीय उदाहरणों को पूर्णांक गणनाओं से स्वतंत्र रूप से जाँचा गया था। शोध के वातावरण में Swift कंपाइलर उपलब्ध नहीं था, इसलिए macOS प्रोग्राम की जाँच की गई, लेकिन वहाँ उसे कंपाइल या चलाया नहीं गया।
रद्दीकरण को दृश्य के जीवनकाल से जोड़ें
गायब होने वाला हैंडलर स्पष्ट रूप से stop() को कॉल करता है। Apple दस्तावेज़ करता है कि onDisappear दृश्य के गायब होने के बाद चलता है; इसका सटीक समय दृश्य के प्रकार पर निर्भर करता है। इसे दृश्य के सफ़ाई हुक के रूप में लें, किसी सटीक विंडो-इवेंट समय-सीमा के रूप में नहीं। दिखाई देने वाला हैंडलर दृश्य के हर बार दिखने पर मौजूदा इनपुट को फिर से शेड्यूल करता है। SwiftUI onDisappear का दस्तावेज़ीकरण
रद्दीकरण कैच जानबूझकर साझा स्टेटस बदले बिना या सहेजे गए हैंडल को साफ़ किए बिना लौटता है। कोई पुराना रद्द किया गया टास्क नए टास्क के सहेजे जाने के बाद फिर से चल सकता है; उस पुराने टास्क से बिना शर्त सफ़ाई कराने पर नया हैंडल मिट सकता है। इस प्रोग्राम में, शेड्यूलिंग और रोकने वाले तरीके रद्द किए गए हैंडल साफ़ करते हैं, और केवल सफलतापूर्वक पूरा होने वाला टास्क अपना सक्रिय हैंडल साफ़ करता है।
डीबाउंस यह नियंत्रित करता है कि काम कब शुरू हो। इससे लंबी समकालिक गणना प्रतिक्रियाशील नहीं बनती और न ही पहले से असाइन किए गए परिणाम को रद्दीकरण वापस ले सकता है। इस सीमित फ़ॉर्मूले के लिए उपयोगी तरीका संक्षिप्त है: पिछला हैंडल रद्द करें, कैप्चर किए गए इनपुट को सत्यापित करें, शांत अवधि तक निलंबित रहें, रद्दीकरण पर बाहर निकलें और मुख्य ऐक्टर पर छोटी गणना का परिणाम असाइन करें। इससे Mac इंटरफ़ेस स्थिर इनपुट का अनुमानित पूर्वावलोकन देता है।
स्रोत
- 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।