विलंबित Swift गणना अपने स्वामी को जीवित रखती है, जब उसका टास्क उस स्वामी को मज़बूती से कैप्चर करता है। प्रतीक्षा के दौरान स्वामी को मुक्त होने देने के लिए कमज़ोर कैप्चर का उपयोग करें, या जब गणना को उसके बिना पूरा होना चाहिए, तो स्वतंत्र मान-स्नैपशॉट कैप्चर करें।
छोटे डेस्कटॉप गणना-पूर्वावलोकन पर काम कर रहे Mac डेवलपर के लिए व्यावहारिक काम यह है कि इस जीवनकाल का चुनाव सोच-समझकर किया जाए। यह उदाहरण थोड़ी देर प्रतीक्षा करता है, कृत्रिम मान [4, 8, 12] जोड़ता है और कुल को फ़ॉर्मैट करता है। सब कुछ स्थानीय रूप से मेमोरी में चलता है।
आपकी गणना का जीवनकाल कैसा होना चाहिए?
Swift की कैप्चर सूचियाँ आपको यह निर्दिष्ट करने देती हैं कि क्लोज़र किन चीज़ों को अपने पास रखता है। मज़बूत कैप्चर संदर्भित इंस्टेंस को थामे रखता है; कमज़ोर संदर्भ उसे डीलोकेट होने देता है और उसके बाद nil बन जाता है। ये भाषा के प्रलेखित व्यवहार हैं, गणना कितनी देर प्रतीक्षा करती है इससे स्वतंत्र। स्वचालित संदर्भ गणना
कैप्चर सिंटैक्स चुनने से पहले व्यवहार तय करें। नीचे दी गई निर्णय तालिका विलंबित डेस्कटॉप पूर्वावलोकन पर भाषा के इन नियमों को लागू करती है:
| इच्छित व्यवहार | कैप्चर रणनीति | कॉलर द्वारा अपना स्वामी रिलीज़ करने के बाद क्या होता है? |
|---|---|---|
| उसी स्वामी का उपयोग करके पूरा करें | [self] |
टास्क अपने काम के लिए स्वामी को उपलब्ध रखता है। |
| केवल तभी जारी रखें, जब स्वामी उपलब्ध रहे | [weak self] |
प्रतीक्षा के बाद टास्क जाँचता है कि स्वामी अब भी मौजूद है या नहीं। |
| शेड्यूल करते समय चुने गए मानों का उपयोग करके पूरा करें | [samples = self.samples] |
टास्क इस स्वामी को थामे बिना कैप्चर किए गए मानों को अपने पास रखता है। |
स्नैपशॉट वाली पंक्ति में एक ज़रूरी शर्त है: यह उदाहरण पूर्णांकों की एक ऐरे कैप्चर करता है। क्लोज़र बनते समय कैप्चर-सूची के एक्सप्रेशन का मूल्यांकन होता है। इसके बजाय किसी क्लास इंस्टेंस को कैप्चर करने पर उसका संदर्भ थाम लिया जाएगा; उसकी परिवर्तनीय सामग्री की स्वतंत्र कॉपी नहीं बनेगी। एक्सप्रेशन
मानों से जुड़ा एक अलग सवाल भी है। नीचे दिए गए मज़बूत और कमज़ोर संस्करण विलंब के बाद स्वामी के मान पढ़ते हैं। स्नैपशॉट संस्करण शेड्यूल करते समय वाले मानों का उपयोग करता है। यदि प्रतीक्षा के दौरान पूर्वावलोकन के इनपुट बदल सकते हैं, तो तय करें कि परिणाम को किस व्याख्या का प्रतिनिधित्व करना चाहिए।
अपने Mac पर पूरी स्थानीय तुलना चलाएँ
macOS 13 या बाद का संस्करण और Swift 6 या बाद का इंस्टॉल किया हुआ टूलचेन इस्तेमाल करें। यहाँ उपयोग किया गया अवधि-आधारित Task.sleep(for:) ओवरलोड macOS 13 और उसके बाद के संस्करणों के लिए प्रलेखित है। यह अंतर्निहित थ्रेड को ब्लॉक किए बिना टास्क को सस्पेंड करता है और प्रतीक्षा पूरी होने से पहले रद्द किए जाने पर CancellationError थ्रो करता है। sleep(for:tolerance:clock:)
नीचे दिए गए पूरे प्रोग्राम को CaptureDemo.swift के रूप में सेव करें। कैलकुलेटर, टास्क क्लोज़र और डेमॉन्स्ट्रेशन @MainActor पर चलते हैं, जिससे उनकी स्टेट तक पहुँच क्रमबद्ध रहती है। Swift क्लास-स्तर के मेन-एक्टर आइसोलेशन और यहाँ इस्तेमाल किए गए स्पष्ट क्लोज़र एनोटेशन, दोनों को प्रलेखित करता है। कन्करेंसी
import Foundation
enum CapturePolicy: String {
case strong, weak, snapshot
}
@MainActor
func formatTotal(_ samples: [Int]) -> String {
let total = samples.reduce(0, +)
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.usesGroupingSeparator = false
formatter.maximumFractionDigits = 0
guard let text = formatter.string(from: NSNumber(value: total)) else {
return "Formatting unavailable"
}
return "Total: \(text)"
}
@MainActor
final class Calculator {
var samples = [4, 8, 12]
func start(_ policy: CapturePolicy) -> Task<String, Error> {
switch policy {
case .strong:
return Task { @MainActor [self] in
try await Task.sleep(for: .milliseconds(300))
try Task.checkCancellation()
return formatTotal(self.samples)
}
case .weak:
return Task { @MainActor [weak self] in
try await Task.sleep(for: .milliseconds(300))
try Task.checkCancellation()
guard let self else {
return "Owner unavailable"
}
return formatTotal(self.samples)
}
case .snapshot:
return Task { @MainActor [samples = self.samples] in
try await Task.sleep(for: .milliseconds(300))
try Task.checkCancellation()
return formatTotal(samples)
}
}
}
}
@main
struct CaptureDemo {
@MainActor
static func demonstrate(
_ policy: CapturePolicy,
cancel: Bool = false
) async {
var owner: Calculator? = Calculator()
weak var probe = owner
let task = owner!.start(policy)
owner = nil
let label = cancel ? "\(policy.rawValue), cancelled" : policy.rawValue
print("\(label) retains owner: \(probe != nil)")
if cancel {
task.cancel()
}
do {
let result = try await task.value
print(result)
} catch is CancellationError {
print("Cancelled")
} catch {
print("Calculation failed: \(error)")
}
}
@MainActor
static func main() async {
await demonstrate(.strong)
await demonstrate(.weak)
await demonstrate(.snapshot)
await demonstrate(.snapshot, cancel: true)
}
}
अंकगणित पूर्णांकों को जोड़कर एक योग निकालने के लिए मानक लाइब्रेरी के reduce(0, +) का उपयोग करता है। Foundation का NumberFormatter उस योग को डिस्प्ले टेक्स्ट में बदलता है; उसका string(from:) मेथड एक वैकल्पिक स्ट्रिंग लौटाता है, इसलिए प्रोग्राम फ़ॉर्मैटिंग की विफलता स्पष्ट रूप से रिपोर्ट करता है। नमूने का कुल 24 होता है। reduce(::), NumberFormatter, string(from:)
Terminal में फ़ाइल वाले फ़ोल्डर में जाएँ और ये कमांड चलाएँ। टार्गेट आपके Mac के प्रोसेसर आर्किटेक्चर के लिए macOS 13 निर्दिष्ट करता है। -parse-as-library विकल्प फ़ाइल के स्पष्ट @main एंट्री पॉइंट को इस्तेमाल करने देता है। Swift का कंपाइलर सोर्स फ़ाइलें और कमांड-लाइन विकल्प स्वीकार करता है; swiftc -help आपके टूलचेन के साथ इंस्टॉल किए गए विकल्प दिखाता है। Swift कमांड-लाइन मैनुअल
swift --version
swiftc -parse-as-library -swift-version 6 -target "$(uname -m)-apple-macos13.0" CaptureDemo.swift -o CaptureDemo
./CaptureDemo
अपेक्षित एप्लिकेशन आउटपुट नीचे दिखाया गया है। यह कैप्चर और रद्दीकरण के नियमों से निकाला गया है; इस पांडुलिपि के शोध परिवेश में Swift कंपाइलर उपलब्ध नहीं था, इसलिए प्रोग्राम की जाँच की गई, लेकिन यहाँ चलाया नहीं गया।
strong retains owner: true
Total: 24
weak retains owner: false
Owner unavailable
snapshot retains owner: false
Total: 24
snapshot, cancelled retains owner: false
Cancelled
कमज़ोर probe स्वामित्व को बिना कोई अतिरिक्त मज़बूत संदर्भ जोड़े देखता है। प्रत्येक डेमॉन्स्ट्रेशन टास्क का इंतज़ार करने से पहले अपना मज़बूत संदर्भ साफ़ करता है। चूँकि डेमॉन्स्ट्रेशन और टास्क क्लोज़र एक ही एक्टर का उपयोग करते हैं, नया बनाया गया क्लोज़र उस समकालिक सेटअप के बीच में नहीं चल सकता। इससे तुलना इस अनुमान पर निर्भर नहीं रहती कि टास्क कितनी जल्दी शुरू होगा। कन्करेंसी
कमज़ोर जाँच की जगह क्यों मायने रखती है?
कमज़ोर शाखा में guard let self विलंब के बाद आता है। इसलिए टास्क स्वामी को मज़बूती से थामे बिना प्रतीक्षा कर सकता है। यदि स्वामी जा चुका है, तो स्पष्ट Owner unavailable परिणाम उस गणना को समाप्त कर देता है।
अनरैप को विलंब से पहले ले जाने पर जीवनकाल बदल जाता है। नीचे दिया गया अंश कमज़ोर शाखा के क्लोज़र बॉडी की जगह इस्तेमाल करने के लिए है; यह कोई अलग प्रोग्राम नहीं है:
// Inside the existing [weak self] task closure:
guard let self else {
return "Owner unavailable"
}
try await Task.sleep(for: .milliseconds(300))
try Task.checkCancellation()
return formatTotal(self.samples)
यहाँ सफल वैकल्पिक बाइंडिंग एक मज़बूत स्थानीय संदर्भ बनाती है, जिसकी सस्पेंशन के बाद फिर ज़रूरत पड़ती है। इसलिए बाइंडिंग सफल होने के बाद प्रतीक्षा के दौरान स्वामी उपलब्ध रहता है। यह Swift के मज़बूत-संदर्भ नियमों का परिणाम है: कमज़ोर कैप्चर तय करता है कि क्लोज़र शुरुआत में स्वामी तक कैसे पहुँचता है, जबकि अनरैप किया गया स्थानीय मान तय करता है कि आगे का कोड उसे कैसे थामता है। स्वचालित संदर्भ गणना
“स्वामी को थामे बिना प्रतीक्षा करें, फिर केवल तभी गणना करें जब वह अब भी मौजूद हो” वाले व्यवहार के लिए, अनरैप को प्रतीक्षा के बाद रखें और तुरंत छोटी गणना करें। अनरैप के बाद अतिरिक्त सस्पेंशन पॉइंट हों, तो जीवनकाल पर फिर से निर्णय लेना होगा।
unowned कैप्चर के लिए ज़रूरी है कि संदर्भित इंस्टेंस हर बार एक्सेस किए जाने पर वैध रहे। यह इस उदाहरण के लिए ज़रूरी वैकल्पिक अनुपस्थिति-जाँच उपलब्ध नहीं कराता, जहाँ विलंब के दौरान स्वामी को रिलीज़ करना जानबूझकर किया गया है। स्वचालित संदर्भ गणना
क्या टास्क हैंडल रिलीज़ करने से गणना रुक जाती है?
यदि आप टास्क का हैंडल छोड़ दें, तब भी टास्क चलता रहता है। हैंडल अपने पास रखने से आप उसके परिणाम का इंतज़ार कर सकते हैं या रद्दीकरण का अनुरोध कर सकते हैं। Apple यह भी प्रलेखित करता है कि पूरा हुआ टास्क अपने ऑपरेशन क्लोज़र और उस क्लोज़र द्वारा थामे गए संदर्भों को तुरंत रिलीज़ कर देता है। इसलिए सीमित अवधि का मज़बूत कैप्चर काम पूरा होने तक स्वामी का जीवनकाल बढ़ाता है, लेकिन ज़रूरी नहीं कि इससे कोई स्थायी संदर्भ चक्र बने। टास्क
रद्दीकरण, स्वामित्व से अलग व्यवहार है। अंतिम डेमॉन्स्ट्रेशन हैंडल के ज़रिए रद्दीकरण का अनुरोध करता है। थ्रो करने वाली स्लीप और उसके बाद का Task.checkCancellation() ऑपरेशन को रोकने के मौके देते हैं, और कॉलर उस नतीजे की रिपोर्ट करता है। Swift में रद्दीकरण सहकारी होता है: उसका अनुरोध मनमाने गणना को ज़बरदस्ती बीच में नहीं रोकता। टास्क
यदि रद्दीकरण से परिणाम रुकना चाहिए, तो थ्रो करने वाली स्लीप को try? से बदलकर बिना शर्त आगे न बढ़ें। उस त्रुटि को दबाने से गणना तक पहुँचना संभव होगा, जब तक कोई दूसरी रद्दीकरण-जाँच उसे न रोके। इसी तरह, स्वामी के वेरिएबल को nil असाइन करना रद्दीकरण का अनुरोध नहीं है।
डेस्कटॉप पूर्वावलोकन के लिए, ये भेद तीन उपयोगी नियंत्रण देते हैं: स्वामी आवंटित रहता है या नहीं, गणना कौन-से इनपुट मान इस्तेमाल करती है, और ऑपरेशन रुकना चाहिए या नहीं। कमज़ोर कैप्चर से इन तीनों का नियंत्रण होने की अपेक्षा करने के बजाय, हर बात स्पष्ट रूप से चुनें।
डेस्कटॉप पूर्वावलोकन के लिए आपको क्या बदलना चाहिए?
जब लंबित पूर्वावलोकन का महत्व केवल तभी हो जब उसका स्वामी उपलब्ध रहे, तो कमज़ोर संस्करण इस्तेमाल करें। जब चुने गए कृत्रिम इनपुट से स्वतंत्र रूप से परिणाम मिलना चाहिए, तो स्नैपशॉट संस्करण इस्तेमाल करें। जब ऑपरेशन पूरा करने के लिए स्वामी की सचमुच ज़रूरत हो, तो मज़बूत संस्करण इस्तेमाल करें।
ऑब्जेक्ट का जीवनकाल और इंटरफ़ेस की दृश्यता भी अलग-अलग हैं। विंडो बंद होने से अपने आप यह साबित नहीं होता कि कैलकुलेटर के हर मज़बूत संदर्भ को हटा दिया गया है। यदि पूर्वावलोकन बंद होने पर एप्लिकेशन को लंबित काम रोकना चाहिए, तो उस एप्लिकेशन इवेंट को अपने पास रखे टास्क हैंडल के cancel() मेथड से जोड़ें; गणना को फिर भी रद्दीकरण में सहयोग करना होगा। टास्क
इस उदाहरण का वर्कलोड छोटा रखें। मेन-एक्टर आइसोलेशन स्टेट तक पहुँच को आसान बनाता है और प्रतीक्षा सस्पेंड होती है, लेकिन समकालिक योग अब भी उसी एक्टर पर चलता है। Swift की कन्करेंसी गाइड बताती है कि मेन एक्टर इंटरफ़ेस का काम भी संभालता है। काफ़ी बड़ी गणना के लिए एक अलग निष्पादन डिज़ाइन चाहिए, जो वर्कर को उपयुक्त मान दे। कन्करेंसी
इस स्थानीय गणना के लिए निर्णायक परीक्षण सरल है: विलंबित ऑपरेशन चलने से पहले कॉलर का संदर्भ हटा दें और परिणाम देखें। मज़बूत कैप्चर स्वामी को सुरक्षित रखता है, कमज़ोर कैप्चर स्पष्ट अनुपलब्धता परिणाम की अनुमति देता है, और पूर्णांकों का ऐरे-स्नैपशॉट पूरा करने के लिए ज़रूरी इनपुट ही सुरक्षित रखता है।
स्रोत
- स्वचालित संदर्भ गणना — Swift.org
- एक्सप्रेशन — Swift.org
- कन्करेंसी — Swift.org
- टास्क — Apple Developer
- sleep(for:tolerance:clock:) — Apple Developer
- reduce(::) — Apple Developer
- NumberFormatter — Apple Developer
- string(from:) — Apple Developer
- Swift कमांड-लाइन मैनुअल — Swift project