রিটেন সাইকেল (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 আছে। সকল ViewControllerয়ের জন্য এই অভ্যাস প্রজেক্ট টেমপ্লেটে যোগ করুন।
সাধারণ প্রশ্নাবলী
Retain cycle হল একটি বিশিষ্ট ARC সমস্যা যেখানে strong রিফারেন্সের একটি বন্ধ লূপ মুক্তি অবরুদ্ধ করে। GC-এ, সংগ্রহক রূট সেট থেকে উপগম্যতা বিশ্লেষণ করে, রিফারেন্স কাউন্ট নয় — তাই চক্র লিক নয়। ARC-এ, যাইহোক, যে কোন পৃথক চক্র একটি গারন্টিযুক্ত লিক।
একটি weak রিফারেন্স একটি অবজেক্টের retain count বাড়ায় না। যদি আপি একটি চক্রের একটি strong রিফারেন্সকে weak দিয়ে প্রতিস্থাপন করেন, তবে প্রত্যেকটি অবজেক্টের retain count শূন্য হতে পারে। অবজেক্টটি মুক্ত হোওয়ার পর, weak রিফারেন্সটি স্বযজং নিলে সেট হয়ে যায়, যা মুক্ত মেমরিতে প্রবেশ প্রতিরোধ করে।
হ্যাঁ, একটি retain cycle যে কোনো সংখ্যাক অবজেক্ট অন্তর্ভুক্ত করতে পারে: A → B → C → A। একে ভাঙতে, আপনাকে কেবল একটি লিঙ্ক ভাঙতে হবে — যে কোন স্ট্রং রিফারেন্সকে 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 অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন