Swift पारंपरिक गार्बेज कलेक्टर का उपयोग क्यों नहीं करता?
\nअगर आप Python या Java से Swift की दुनिया में आए हैं, तो आपको शायद यह जानकर हैरानी हुई होगी कि इस भाषा में क्लासिकल गार्बेज कलेक्टर (Garbage Collector) नहीं है। इसके बजाय, Apple ने Automatic Reference Counting (ARC) नामक एक तंत्र लागू किया है - यानी स्वचालित रेफरेंस गिनती। और यह कोई सनक नहीं है: मोबाइल उपकरणों पर, प्रोसेसर का हर टिक और मेमोरी का हर बाइट मायने रखता है। पारंपरिक GC एप्लिकेशन को सफाई के दौरान 'फ्रीज' कर देता है, जो iOS के लिए 60 FPS पर स्मूथ एनिमेशन की आवश्यकता को देखते हुए एक आपदा होगी।
\nइस लेख में, हम समझेंगे कि Swift में मेमोरी प्रबंधन वास्तव में कैसे काम करता है, साइक्लिक रेफरेंसेज (चक्रीय संदर्भ) के कारण कहाँ परेशानी होती है, और ऐसा कोड कैसे लिखें जो मेमोरी को 'लीक' न करे।
\n\nARC: यह अंदर से कैसे काम करता है?
\nARC सामान्य अर्थों में गार्बेज कलेक्टर नहीं है। यह कंपाइलेशन के समय काम करता है: कंपाइलर सही स्थानों पर retain और release निर्देश डालता है। लेकिन एक बारीक बात है: ARC साइक्लिक रेफरेंसेज (retain cycles) को संभाल नहीं सकता। यदि दो ऑब्जेक्ट एक-दूसरे को स्ट्रॉन्ग रेफरेंस से पकड़ते हैं, तो उनका रेफरेंस काउंट कभी शून्य नहीं होगा - मेमोरी लीक हो जाएगी।
उदाहरण: एक सरल क्लास और रेफरेंस गिनती
\nclass Person {\n let name: String\n init(name: String) {\n self.name = name\n print("\(name) मेमोरी में आया")\n }\n deinit {\n print("\(name) मेमोरी से गया")\n }\n}\n\nvar person1: Person? = Person(name: "अलीशा") // retain count = 1\nvar person2 = person1 // retain count = 2\nperson1 = nil // retain count = 1\nperson2 = nil // retain count = 0 → deinit\n\nकंसोल में आप देखेंगे: "अलीशा मेमोरी में आया" और कुछ समय बाद "अलीशा मेमोरी से गया"। सब कुछ सही है।
\n\nसाइक्लिक रेफरेंसेज: मेमोरी का सबसे बड़ा दुश्मन
\nकल्पना करें: आपके पास User और Account हैं। उपयोगकर्ता के पास एक खाता है, और खाता अपने मालिक को जानता है। यदि दोनों प्रॉपर्टी स्ट्रॉन्ग रेफरेंस हैं, तो एक बंद घेरा बन जाता है:
class User {\n let name: String\n var account: Account?\n init(name: String) { self.name = name }\n deinit { print("User \(name) हटा दिया गया") }\n}\n\nclass Account {\n let number: String\n var owner: User?\n init(number: String) { self.number = number }\n deinit { print("Account \(number) हटा दिया गया") }\n}\n\nvar user: User? = User(name: "बोरिस")\nvar account: Account? = Account(number: "12345")\nuser?.account = account\naccount?.owner = user\n\nuser = nil // User हटा नहीं गया! account?.owner अभी भी रेफरेंस पकड़े हुए है\naccount = nil // Account हटा नहीं गया! user?.account रेफरेंस पकड़े हुए है\n// मेमोरी लीक!\n\nकंसोल में कोई deinit संदेश नहीं दिखेगा। ऑब्जेक्ट हमेशा के लिए मेमोरी में फंस गए हैं। यह एक retain cycle है - Swift में मेमोरी प्रबंधन की एक क्लासिक समस्या।
समाधान क्या है? Weak और Unowned
\nसमाधान यह है कि एक रेफरेंस को कमज़ोर (weak) या बिना मालिक (unowned) बना दिया जाए। अंतर:
- \n
- weak - हमेशा optional होता है, जब ऑब्जेक्ट हटा दिया जाता है तो स्वचालित रूप से
nilहो जाता है। सुरक्षित। \n - unowned - optional नहीं होता, यह मानता है कि ऑब्जेक्ट अधिक समय तक जीवित रहेगा। यदि हटाए गए ऑब्जेक्ट को एक्सेस किया गया तो क्रैश हो जाएगा। \n
class Account {\n let number: String\n weak var owner: User? // ← कमज़ोर रेफरेंस\n init(number: String) { self.number = number }\n deinit { print("Account \(number) हटा दिया गया") }\n}\n// अब user = nil करने पर User रिलीज़ हो जाएगा, और account?.owner nil हो जाएगा\n\n\nगार्बेज कलेक्टर बनाम ARC: कौन तेज़ है?
\nसच कहूँ तो: ARC क्लासिकल अर्थों में 'गार्बेज कलेक्टर' नहीं है। असली GC (जैसे Java, Go या Python में) रनटाइम पर काम करता है: यह समय-समय पर हीप को स्कैन करता है, अप्राप्य ऑब्जेक्ट्स ढूंढता है और उन्हें मुक्त करता है। इसके लिए रुकने (stop-the-world) की आवश्यकता होती है।
\nARC एक स्टैटिक तंत्र है - यह कंपाइल टाइम पर ही तय कर देता है कि कब मेमोरी खाली करनी है। इसका मतलब है कि रनटाइम पर कोई अतिरिक्त ओवरहेड नहीं है, लेकिन इसके लिए डेवलपर को साइक्लिक रेफरेंसेज को मैन्युअल रूप से प्रबंधित करना होता है।