परिचय: जब आप फ़ीड स्क्रॉल कर रहे होते हैं तो आपका iPhone क्यों नहीं हैंग होता?
\n\nक्या आपने कभी सोचा है कि आधुनिक iOS ऐप्स तब भी धीमे क्यों नहीं होते, जब वे एक साथ दर्जनों चित्र लोड कर रहे हों, टैप प्रोसेस कर रहे हों और डेटाबेस में डेटा लिख रहे हों? इसका उत्तर सरल है — समवर्तीता। Swift (और न केवल) की दुनिया में यह सबसे गर्म विषयों में से एक है। और यदि आप Swift में लिखते हैं, लेकिन फिर भी थ्रेड्स, गोरूटीन्स और नए async/await में भ्रमित हो जाते हैं — तो क्लब में आपका स्वागत है। आज हम सब कुछ स्पष्ट कर देंगे, ताकि आपका कोड उड़े, न कि रेंगे।
इस लेख में हम केवल सूखी परिभाषाएँ सूचीबद्ध नहीं करेंगे। हम वास्तविक उदाहरण देखेंगे, दृष्टिकोणों की तुलना करेंगे और मुख्य प्रश्न का उत्तर देंगे: अपने प्रोजेक्ट के लिए क्या चुनें — पुराना अच्छा GCD या नया Swift Concurrency?
\n\n\n\nथ्रेड्स: वह नींव जिस पर सब कुछ टिका है
\n\nआधार से शुरू करते हैं। थ्रेड प्रसंस्करण की सबसे छोटी इकाई है जिसे ऑपरेटिंग सिस्टम निष्पादित कर सकता है। एक कन्वेयर बेल्ट की कल्पना करें: एक थ्रेड — एक लाइन जिस पर कार्य चलते हैं। यदि आपके पास एक सिंगल-कोर प्रोसेसर है, तो थ्रेड बारी-बारी से निष्पादित होंगे, लेकिन बहुत तेज़ी से स्विच करते हुए, समानता का भ्रम पैदा करेंगे। मल्टी-कोर प्रोसेसर पर, थ्रेड वास्तव में समानांतर रूप से निष्पादित हो सकते हैं।
\n\nSwift में लंबे समय तक थ्रेड्स के साथ काम करने का मुख्य उपकरण Grand Central Dispatch (GCD) था। आप बस एक कतार बनाते थे और उसमें एक क्लोज़र भेजते थे:
\n\nDispatchQueue.global(qos: .background).async {\n // पृष्ठभूमि में भारी काम\n let result = heavyComputation()\n DispatchQueue.main.async {\n // मुख्य थ्रेड पर UI अपडेट करना\n self.label.text = result\n }\n}\n\nसरल है, है ना? लेकिन GCD में कुछ बारीकियाँ हैं:
\n- \n
- आप स्वयं मुख्य थ्रेड पर वापस आने के लिए जिम्मेदार हैं (इसे भूलना आसान है और क्रैश हो सकता है)। \n
- कार्यों को रद्द करने का कोई अंतर्निहित समर्थन नहीं है (फ़्लैग मैन्युअल रूप से लिखने पड़ते हैं)। \n
- क्लोज़र्स के नेस्टिंग के बिना निर्भर संचालन की श्रृंखलाओं को संसाधित करना कठिन है (तथाकथित "callback hell")। \n
फिर भी, GCD आज भी जीवित है और लीगेसी प्रोजेक्ट्स में उपयोग किया जाता है। लेकिन आधुनिक Swift कहीं अधिक सुरुचिपूर्ण कुछ प्रदान करता है।
\n\nSwift में गोरूटीन्स? नहीं, यह Go नहीं है
\n\nजब गोरूटीन्स की बात होती है, तो आमतौर पर Go याद आता है। Swift में गोरूटीन्स का कोई सीधा एनालॉग नहीं है, लेकिन हल्के-फुल्के कार्य (tasks) हैं, जो बहुत समान व्यवहार करते हैं। वे किसी विशिष्ट थ्रेड से बंधे नहीं होते — सिस्टम स्वयं तय करता है कि कार्य को किस थ्रेड पर निष्पादित करना है, और प्रत्येक कॉल के लिए नए थ्रेड बनाए बिना उन्हें थ्रेड्स के बीच स्विच कर सकता है।
\n\nथ्रेड्स के विपरीत, जिनमें से प्रत्येक का स्टैक लगभग 8 KB होता है, Swift Concurrency के कार्य व्यावहारिक रूप से कुछ भी नहीं लेते हैं। आप मेमोरी को ओवरफ्लो करने के जोखिम के बिना हजारों कार्य चला सकते हैं। यही वह "बिना दर्द के समवर्तीता" है।
\n\nदेखें यह कैसा दिखता है:
\n\nTask {\n let image = await downloadImage(from: url)\n await MainActor.run {\n self.imageView.image = image\n }\n}\n\nध्यान दिया? कोई DispatchQueue.main.async नहीं। बस await MainActor.run। साफ, समझने योग्य, सुरक्षित।
async/await: वह क्रांति जो हुई
\n\nSwift 5.5 के आगमन के साथ, async/await के लिए अंतर्निहित समर्थन आया। यह केवल सिंटैक्टिक शुगर नहीं है — यह प्रतिमान बदलाव है। अब आप एसिंक्रोनस कोड इस तरह लिख सकते हैं जैसे कि वह सिंक्रोनस हो, लेकिन थ्रेड को ब्लॉक किए बिना।
\n\nएक एसिंक्रोनस फ़ंक्शन घोषित करना सरल है:
\n\nfunc fetchUserData() async throws -> User {\n let url = URL(string: "https://api.example.com/user")!\n let (data, _) = try await URLSession.shared.data(from: url)\n return try JSONDecoder().decode(User.self, from: data)\n}\n\nऔर इसे कॉल करना और भी सरल है:
\n\nTask {\n do {\n let user = try await fetchUserData()\n print("User nam