रिटेन साइकल (Retain Cycle) ARC में एक स्थिति है जहां दो या अधिक ऑब्जेक्ट strong संदर्भों के माध्यम से एक-दूसरे का संदर्भ लेते हैं, एक बंद लूप बनाते हुए। Apple Memory Management Guide, 2026 के अनुसार, retain cycle चक्र में सभी ऑब्जेक्टों की रिलीज को अवरुद्ध करता है क्योंकि प्रत्येक का retain count ≥ 1 होता है। GC में मेमरी लीक के विपरीत, retain cycle गारंटी देता है कि ऑब्जेक्ट तब तक जीवित रहेंगे जब तक चक्र का कम से कम एक बाहरी सदस्य जीवित है — और यहाँ तक कि सभी बाहरी संदर्भों के खो जाने के बाद भी, यदि चक्र पृथक है।
मुख्य बातें
Retain Cycle एक ऐसी स्थिति है जिसमें दो या अधिक ऑब्जेक्ट strong संदर्भों के माध्यम से एक-दूसरे के मालिक होते हैं, एक बंद निर्भरता ग्राफ बनाते हुए। ARC इनमें से किसी भी ऑब्जेक्ट को मुक्त नहीं कर सकता क्योंकि प्रत्येक का retain count हमेशा ≥ 1 होता है: ऑब्जेक्ट A, B को रखता है, B, A को रखता है, और उनके काउंटर कभी शून्य नहीं होते।
यह समस्या विशेष रूप से संदर्भ गणना प्रणालियों (ARC, MRR) में होती है। कचरा संग्रहण (Garbage Collection) में, संग्रहक रूट सेट से संदर्भ ग्राफ के माध्यम से अपथ्यता निर्धारित करता है — चक्र कोई बाधा नहीं हैं। ARC में, हालाँकि, एक चक्र लीक के समान है, क्योंकि निर्धारित मुक्ति गणना के माध्यम से चाक्रिक निर्भरताओं को हल नहीं कर सकती।
WWDC 2012 Session 406 के अनुसार, retain cycle Objective-C और Swift एप्लिकेशनों में मेमरी लीक का सबसे सामान्य कारण है। विशिष्ट परिदृश्य: डेलीगेट के साथ मात्र-पित्र संबंध, self को कैप्चर करने वाले क्लोजर, और द्वि-दिशात्मक संबंधों वाली स्तरित आर्किटेक्चर्स।
आइए क्लासिक retain cycle परिदृश्यों की जाँच करें जिनका सामना प्रत्येक iOS डेवलपर करता है। इन पैटर्नों को समझना ARC के साथ सुरक्षित कोड लिखने का आधार है।
क्लासिक परिदृश्य: एक मातृ ऑब्जेक्ट (उदाहरण के लिए, UIViewController) एक चाइल्ड ऑब्जेक्ट बनाता है और उसका डेलीगेट बन जाता है। यदि दोनों strong संदर्भों का उपयोग करते हैं, तो retain cycle होता है। समाधान — डेलीगेट weak होना चाहिए।
// त्रुटि: strong delegate के माध्यम से retain cycle
protocol ChildDelegate: AnyObject { }
class ParentVC: UIViewController, ChildDelegate {
var child: ChildVC?
func showChild() {
child = ChildVC()
child?.delegate = self // Parent → Child (strong)
} // Child → Parent (delegate के माध्यम से strong)
} // ⚠️ Retain cycle!
class ChildVC: UIViewController {
var delegate: ChildDelegate? // ❌ strong डिफ़ॉल्ट
}
// सुधार: weak delegate
class ChildVC: UIViewController {
weak var delegate: ChildDelegate? // ✅ weak — धारण नहीं करता
}
उदाहरण में, ParentVC child संपत्ति के माध्यम से ChildVC पर strong संदर्भ रखता है। ChildVC delegate के माध्यम से ParentVC पर strong संदर्भ रखता है। चक्र बंद है। सुधार: weak var delegate — संदर्भ retain count नहीं बढ़ाता, और ParentVC मुक्त हो सकता है।
NSTimer retain cycle का एक क्लासिक स्रोत है। टाइमर अपने लक्ष्य (आमतौर पर self) को रोके रखता है, और लक्ष्य एक संपत्ति के माध्यम से टाइमर को रोके रखता है। भले ही टाइमर एक-बार का हो, यह invalidate काल किए जाने तक मुक्त नहीं होगा। समाधान: deinit या viewDidDisappear में हमेशा timer.invalidate() को कॉल करें।
कैस्केडिंग स्वामित्व वाले आर्किटेक्चर्स (कोआर्डिनेटर, रूटर) में, अक्सर मल्टी-स्टेप चक्र होते हैं: Coordinator → ViewController → ViewModel → Coordinator (कॉलबैक के माध्यम से)। श्रृंखला में प्रत्येक strong संदर्भ को सावधानीपूर्वक चुना जाना चाहिए — किसी भी कड़ी में एक weak संदर्भ चक्र को तोड़ देता है।
Swift में क्लोजर बाहरी चर को strong संदर्भ से कैप्चर करते हैं। यदि किसी ऑब्जेक्ट की संपत्ति के रूप में एक क्लोजर (उदाहरण के लिए, completion handler) संग्रहीत किया जाता है और self को कैप्चर करता है, तो यह एक retain cycle बनाता है: self → closure → self।
आधुनिक Swift डेवलपमेंट में यह retain cycle का सबसे आम स्रोत है। यह अप्रत्यक्ष रूप से होता है — एक डेवलपर क्लोजर में self के कैप्चर पर ध्यान नहीं दे सकता, खासकर जब आप स्पष्ट self के बिना शॉर्टहैंड सिंटेक्स का उपयोग करते हैं।
class DownloadService {
var onComplete: ((Data) -> Void)?
var result: Data?
func startDownload() {
// ❌ Retain cycle: self → onComplete → self
onComplete = { data in
self.result = data
self.notifyUI()
}
// ✅ सुधार: weak self के साथ कैप्चर सूची
onComplete = { [weak self] data in
guard let self else { return }
self.result = data
self.notifyUI()
}
}
func notifyUI() { }
}
एक कैप्चर सूची [weak self] क्लोजर के अंदर self का एक कमजोर संदर्भ बनाती है। यदि क्लोजर के निष्पादन से पहले DownloadService मुक्त हो जाता है, तो self nil हो जाता है, और कोड guard के माध्यम से सुरक्षित रूप से बाहर निकलता है। Swift में असिंक्रोनस क्लोजर के लिए यह मानक पैटर्न है — जब भी किसी क्लोजर को संपत्ति के रूप में संग्रहीत किया जाता है, तो इसे लागू किया जाना चाहिए।
unowned self weak self का एक विकल्प है जब self के क्लोजर से अधिक जीवित रहने की गारंटी होती है। उदाहरण: सिंक्रोनस क्लोजर जो तुरंत निष्पादित होते हैं (sorted, filter)। ऐसे मामलों में self निश्चित रूप से जीवित है, और unowned सुरक्षित है। हालाँकि, unowned मुक्त ऑब्जेक्ट तक पहुँचने पर क्रैश करता है — इसलिए weak को डिफ़ॉल्ट सुरक्षित विकल्प माना जाता है।
एप्लिकेशन प्रदर्शन के लिए प्रारंभिक चरण में retain cycle का पता लगाना अत्यंत महत्वपूर्ण है। आइए iOS डेवलपमेंट में चाक्रिक संदर्भों की पहचान के लिए मुख्य उपकरणों और तकनीकों की समीक्षा करें।
Xcode Memory Debugger (Debug Memory Graph) एक दृश्य उपकरण है जो मेमरी में ऑब्जेक्टों का ग्राफ उनके संदर्भों के साथ दिखाता है। एक retain cycle strong तीरों की एक बंद श्रृंखला के रूप में दिखाई देता है। लॉन्च करने के लिए: ऐप चलने के दौरान Debug area पैनल में Debug Memory Graph बटन पर क्लिक करें। प्रत्येक ऑब्जेक्ट अपने प्रकार, पता और संदर्भों की सूची के साथ दिखाया जाता है।
Instruments Leaks स्वचालित लीक पता लगाने के लिए एक प्रोफाइलर है। यह आवंटन रिकॉर्ड करता है और वास्तविक समय में संदर्भ ग्राफ का विश्लेषण करता है। यह ना केवल retain cycle बल्कि भूले हुए संदर्भों, अमुक्त ViewController और अन्य लीक्स का भी पता लगाता है। Leaks सटीक ऑब्जेक्ट और धारण श्रृंखला को इंगित करता है।
सबसे सरल तरीका हर मुख्य क्लास के deinit में print जोड़ना है। यदि किसी ऑब्जेक्ट के नष्ट होने की उम्मीद पर deinit कॉल नहीं होता है, तो एक retain cycle है। इस विधि के लिए किसी उपकरण की आवश्यकता नहीं है और यह प्रारंभिक निदान के लिए प्रभावी है।
| उपकरण | प्रकार | कब उपयोग करें |
|---|---|---|
| Memory Debugger | दृश्य ग्राफ | नेविगेशन के बाद मैनुअल जाँच |
| Instruments Leaks | स्वचालित विश्लेषण | रिग्रेशन परीक्षण, CI |
| deinit print | मैनुअल लॉगिंग | डेवलपमेंट, कोड समीक्षा |
| Malloc Scribble | रनटाइम फ्लैग | use-after-free की डिबगिंग |
अनुशंसित दृष्टिकोण: विकास के दौरान deinit लॉगिंग, मैनुअल परीक्षण के दौरान Memory Debugger, और स्वचालित रिग्रेशन लीक पता लगाने के लिए CI/CD पाइपलाइन में Instruments Leaks का उपयोग करें।
प्रोडक्शन में सुधारने की तुलना में retain cycle को रोकना आसान है। यहाँ कुछ नियम हैं जो चाक्रिक संदर्भों के जोखिम को कम से कम करते हैं।
सभी डेलीगेट और dataSource weak होने चाहिए। यह नियम UIKit में बनाया गया है: Apple SDK में सभी डेलीगेट प्रोटोकॉल weak संपत्तियों के साथ घोषित किए जाते हैं (UITableView.delegate, UICollectionView.dataSource)। अपने स्वयं के प्रोटोकॉल के लिए, weak var delegate: MyDelegate? का उपयोग करें और प्रोटोकॉल को AnyObject से प्राप्त करें।
कोई भी क्लोजर जो संपत्ति (completion handler, callback) के रूप में संग्रहीत है और self को कैप्चर करता है, कैप्चर सूची में [weak self] का उपयोग करना चाहिए। अपवाद वे क्लोजर हैं जो तुरंत निष्पादित होते हैं और संग्रहीत नहीं होते (sorted, map, filter)। उनके लिए, unowned self सुरक्षित है।
जटिल आर्किटेक्चर्स (VIPER, Coordinators, Redux) में, strong संदर्भों की दिशा का ट्रैक करें। स्वामी अधीनस्थ पर strong संदर्भ रखता है, लेकिन अधीनस्थ को स्वामी का संदर्भ केवल weak या unowned के माध्यम से लेना चाहिए। एकदिशात्मक डेटा प्रवाह संदर्भ प्रबंधन को सरल बनाता है।
// उदाहरण: deinit लॉगिंग के साथ जाँच
class BaseViewController: UIViewController {
deinit {
print("✅ \(type(of: self)) deallocated")
}
}
// उपयोग: सभी ViewController BaseViewController से विरासत पाते हैं
class ProfileVC: BaseViewController {
var viewModel: ProfileViewModel?
var onLogout: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
onLogout = { [weak self] in
self?.dismiss(animated: true)
}
}
}
// ProfileVC बंद करने पर कंसोल में "✅ ProfileVC deallocated" की उम्मीद है
deinit लॉगिंग के साथ एक बेस क्लास तुरंत प्रतिपुष्टि प्रदान करता है। यदि स्क्रीन के बंद होने की उम्मीद पर संदेश प्रकट नहीं होता है, तो इस क्लास में retain cycle है। सभी ViewControllers के लिए इस अभ्यास को प्रोजेक्ट टेम्पलेट में जोड़ें।
अक्सर पूछे जाने वाले प्रश्न
Retain cycle एक विशिष्ट ARC समस्या है जहां strong संदर्भों का एक बंद लूप मुक्ति को अवरुद्ध करता है। GC में, संग्रहक रूट सेट से पहुँचने क्षमता का विश्लेषण करता है, संदर्भ गणनाओं का नहीं — इसलिए चक्र लीक नहीं हैं। ARC में, हालाँकि, कोई भी पृथक चक्र एक गारंटीशुदा लीक है।
एक weak संदर्भ किसी ऑब्जेक्ट के retain count को नहीं बढ़ाता है। यदि आप एक चक्र में एक strong संदर्भ को weak से बदलते हैं, तो प्रत्येक ऑब्जेक्ट का retain count शून्य हो सकता है। ऑब्जेक्ट के मुक्त होने के बाद, weak संदर्भ स्वचालित रूप से nil पर सेट हो जाता है, जो मुक्त मेमरी तक पहुँचने से रोकता है।
हाँ, एक retain cycle किसी भी संख्या में ऑब्जेक्टों को शामिल कर सकता है: A → B → C → A। इसे तोड़ने के लिए, आपको केवल एक कड़ी को तोड़ने की आवश्यकता है — किसी भी strong संदर्भ को weak या unowned से बदलें। उपकरण पूरा ग्राफ दिखाते हैं, न केवल ऑब्जेक्टों के जोड़े।
GCD (Grand Central Dispatch) निष्पादन के बाद क्लोजर को संग्रहीत नहीं रखता है। DispatchWorkItem निष्पादित होता है और जारी हो जाता है, भले ही क्लोजर self को कैप्चर करता हो। Retain cycle केवल तब होता है जब किसी क्लोजर को संपत्ति (क्लास में completion handler) के रूप में संग्रहीत किया जाता है, न कि जब इसे किसी क्यू में भेजा जाता है।
Instruments Leaks हमेशा अस्थायी retain cycle (सेकंड तक चलने वाले) या ब्रिजिंग के माध्यम से C/C++ ऑब्जेक्टों में चाक्रिक संदर्भों का पता नहीं लगा पाता। पूर्ण जाँच के लिए, दृश्य में सभी मुख्य ऑब्जेक्टों के deinit लॉगिंग के साथ Memory Debugger का मैनुअल उपयोग करें।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें