Strong Reference (مرجع قوی) — یک مکانیزم استاندارد مدیریت حافظه است که در آن شیء تا زمانی که حداقل یک مرجع فعال به آن اشاره میکند در حافظه باقی میماند. برخلاف مراجع ضعیف، مرجع قوی شمارنده مراجع شیء را افزایش میدهد و از آزادسازی خودکار آن جلوگیری میکند. به گفته Apple Developer Documentation، ARC به طور خودکار عمر اشیاء را در Swift و Objective-C مدیریت میکند. درک کار مراجع قوی برای جلوگیری از نشت حافظه و وابستگیهای چرخهای در برنامههای موبایل حیاتی است.
نکات اصلی
Strong Reference — نوعی مرجع به شیء است که از نابودی آن توسط جمعآورنده زباله یا سیستم مدیریت حافظه جلوگیری میکند. تا زمانی که حداقل یک مرجع قوی به شیء وجود دارد، حافظه زیر آن آزاد نمیشود. این مکانیزم پایهای است که ARC در Swift و Objective-C و جمعآوری زباله در Java و Kotlin بر آن ساخته شدهاند.
مفهوم مرجع قوی برای تمام زبانهای دارای مدیریت خودکار حافظه اساسی است. در سیستمهای دارای ARC هر مرجع قوی شمارنده مراجع شیء را افزایش میدهد. هنگامی که شمارنده به صفر میرسد، شیء بلافاصله از حافظه خارج میشود. در Java و Kotlin با جمعآورنده زباله، مرجع قوی تضمین میکند که شیء قابل دسترسی است و توسط GC جمعآوری نخواهد شد.
طبق دادههای WWDC 2021، حدود ۳۵٪ از نشتهای حافظه در برنامههای iOS به استفاده نادرست از مراجع قوی و چرخههای نگهداری مرتبط است. در توسعه Android، نشتها از طریق strong reference ضمنی در بستارها و فراخوانیهای بازگشتی دومین علت شایع مشکلات حافظه پس از Context Leak است.
برای کار مؤثر با حافظه لازم است تفاوت بین مراجع strong، weak و unowned را درک کرده و بسته به مالکیت و عمر اشیاء، نوع مرجع را به درستی انتخاب کنید.
قبل از معرفی ARC، توسعهدهندگان به صورت دستی retain و release را برای هر شیء فراخوانی میکردند که منجر به اشتباهات متعددی میشد. ARC که توسط اپل در سال ۲۰۱۱ با انتشار LLVM 3.0 معرفی شد، این فرآیند را با تحلیل گراف مالکیت در مرحله کامپایل خودکار کرد. کامپایلر خود فراخوانیهای retain، release و autorelease را در مکانهای مناسب درج میکند.
طبق Clang Static Analyzer، معرفی ARC تعداد باگهای مرتبط با حافظه در برنامههای iOS را ۷۰٪ کاهش داد. برای توسعهدهنده این به معنای ایمنتر شدن مدیریت حافظه است، اما همزمان نیاز به درک نحوه کار مراجع قوی در پشت صحنه — برای اجتناب از retain cycles — ایجاد شد.
در Kotlin و Java نقش ARC را جمعآورنده زباله ایفا میکند، اما اصل مرجع قوی یکسان باقی میماند: GC Roots — نقاط ورودی هستند که از طریق آنها اشیاء توسط مراجع قوی نگهداری میشوند. تا زمانی که شیء از طریق زنجیره مراجع قوی از GC Root قابل دسترسی باشد، جمعآوری نخواهد شد.
ARC (Automatic Reference Counting) بر اساس اصل شمارش مراجع برای هر شیء در heap کار میکند. هنگامی که یک مرجع قوی جدید به شیء ایجاد میشود، شمارنده افزایش مییابد (retain). هنگامی که مرجع نابود یا بازنویسی میشود، شمارنده کاهش مییابد (release). هنگامی که شمارنده به صفر میرسد، شیء بلافاصله از حافظه حذف میشود.
مثالی در Swift را در نظر بگیرید. هنگام ایجاد یک نمونه از کلاس، ARC حافظه را تخصیص میدهد و retain count را برابر ۱ قرار میدهد. هر تخصیص جدید به متغیر دیگر شمارنده را افزایش میدهد. هنگامی که متغیر از محدوده دید خارج میشود، شمارنده کاهش مییابد:
class ProfileViewController {
var nameLabel: String?
var avatarImage: UIImage?
func loadProfile() {
// retain count = 1 برای نمونه جدید
let user = User(name: "Ivan")
// retain count = 2 پس از تخصیص nameLabel
nameLabel = user.name
// خروج از متد — user از scope خارج میشود، retain count = 1
}
}
در این کد ARC تضمین میکند که شیء User تا زمانی که حداقل یک مرجع قوی به آن وجود دارد در حافظه باقی میماند. هنگامی که تابع loadProfile پایان مییابد، متغیر محلی user نابود میشود، اما nameLabel همچنان شیء را نگه میدارد. حافظه تنها زمانی آزاد میشود که nameLabel از بین برود یا بازنویسی شود.
در Kotlin رفتار مشابه از طریق GC Roots تضمین میشود. تا زمانی که یک زنجیره قابل ردیابی از strong references از ریشه جمعآورنده زباله (مثلاً فیلد ایستا یا نخ فعال) وجود دارد، شیء در حافظه باقی میماند. تفاوت در این است که GC حافظه را فوراً آزاد نمیکند — این کار به صورت ناهمزمان پس از تحلیل دسترسیپذیری انجام میشود.
در ARC آزادسازی به صورت همزمان در لحظه صفر شدن شمارنده رخ میدهد. در Swift و Objective-C دقیقاً میدانید شیء چه زمانی حذف خواهد شد. در Kotlin و Java لحظه آزادسازی غیرقابل پیشبینی است، اما این با طرح انعطافپذیرتر تشخیص وابستگیهای چرخهای در سطح جمعآورنده زباله جبران میشود.
Retain cycle (چرخه نگهداری) — وضعیتی است که در آن دو یا چند شیء دارای مراجع قوی متقابل به یکدیگر هستند. در نتیجه retain count آنها هرگز به صفر نمیرسد و حافظه حتی پس از بینیازی برنامه از اشیاء آزاد نمیشود.
مثال کلاسیک: view controller والد، شیء فرزند را با مرجع قوی نگه میدارد و آن نیز به نوبه خود والد را با مرجع قوی نگه میدارد. این برای موقعیتهای دارای delegate، بستارها و عبارات lambda تو در تو معمول است. طبق Instruments Leaks، retain cycles تا ۶۰٪ از کل نشتهای حافظه در برنامههای استفادهکننده از ARC را تشکیل میدهند.
class ParentViewController: UIViewController {
var child: ChildViewController?
func setupChild() {
child = ChildViewController()
// retain cycle: parent child را نگه میدارد، child parent را از طریق closure نگه میدارد
child?.onEvent = {
self.handleEvent()
}
}
func handleEvent() {}
}
مشکل اینجاست که بستار onEvent self (ParentViewController) را با مرجع قوی میگیرد و خود ParentViewController child را با مرجع قوی نگه میدارد. هر دو شیء هرگز آزاد نخواهند شد. راه حل — استفاده از weak self در بستار برای شکستن چرخه.
در Kotlin چرخههای مشابه هنگام استفاده از لامبداهایی که اشیاء خارجی را میگیرند ایجاد میشود. جمعآورنده زباله JVM ممکن است به مرور چنین چرخههایی را تشخیص دهد، اما فقط اگر اشیاء از GC Roots غیرقابل دسترسی باشند. اگر چرخه با یک نخ فعال یا زمینه UI مرتبط باشد، نشت تا طول عمر برنامه باقی میماند.
درک تفاوت بین انواع مراجع — کلید مدیریت ایمن حافظه است. Strong Reference retain count را افزایش میدهد. Weak Reference retain count را افزایش نمیدهد و هنگام آزادسازی شیء به طور خودکار nil میشود. Unowned Reference نیز retain count را افزایش نمیدهد، اما صفر نمیشود — مراجعه به آن پس از آزادسازی باعث crash میشود.
| نوع مرجع | Retain count | ایمنی | زمان استفاده |
|---|---|---|---|
| Strong | +1 | ایمن (پیشفرض) | مالکیت شیء، رابطه parent ← child |
| Weak | تغییر نمیدهد | صفر شدن خودکار (safe) | Delegateها، callback، مراجع بازگشتی |
| Unowned | تغییر نمیدهد | ریسک crash در مراجعه دیرهنگام | وقتی شیء تضمیناً بیشتر از مالک زنده نیست |
انتخاب نوع مرجع با رابطه مالکیت تعیین میشود. اگر شیء B بخشی از A است و بدون آن نمیتواند وجود داشته باشد — از Strong استفاده کنید. اگر B میتواند مستقل وجود داشته باشد و برای اعلانها به A ارجاع میدهد — از Weak استفاده کنید. Unowned به ندرت استفاده میشود — فقط زمانی که عمر شیء فرزند به طور قطع از عمر والد تجاوز نمیکند.
Apple Developer Documentation توصیه میکند: به طور پیشفرض برای تمام روابط مالکیت از strong استفاده کنید. اگر نیاز به اجتناب از retain cycle دارید — تعیین کنید کدام مرجع باید ضعیف باشد. معمولاً این مرجع بازگشتی در سلسلهمراتب است (child ← parent). در Kotlin نقش مشابهی به WeakReference از java.lang.ref داده میشود که برای کشها و الگوی observer استفاده میشود.
تشخیص retain cycles — گام اول. گام دوم — رفع صحیح آنها. ابزار اصلی مبارزه با چرخههای مراجع قوی، جایگزینی یکی از مراجع با weak یا unowned است. در زبانهای دارای جمعآوری زباله، علاوه بر این از WeakReference با بررسی دستی null قبل از هر دسترسی استفاده میشود.
در Swift و Objective-C رایجترین رفع، افزودن [weak self] در بستارها است. این تضمین میکند که بستار شیء را پس از آزادسازی آن نگه نمیدارد. در Kotlin برای اهداف مشابه از wrapper WeakReference یا پاکسازی صریح مرجع در onDestroy استفاده میشود.
class NetworkService {
func fetchData(completion: @escaping (Data?) -> Void) {
// گرفتن از طریق weak self — retain cycle حذف شد
URLSession.shared.dataTask(
with: URL(string: "https://api.example.com")!
) { [weak self] data, response, error in
guard let self else { return }
completion(data)
}.resume()
}
}
در این مثال [weak self] تضمین میکند که NetworkService توسط بستار پس از عدم نیاز نگه داشته نمیشود. اگر self قبل از تکمیل درخواست آزاد شده باشد — guard let self else { return } بدون فراخوانی completion از بستار خارج میشود.
برای تشخیص retain cycles از Instruments Leaks برای iOS یا Android Profiler + LeakCanary برای Android استفاده کنید. این ابزارها گراف دقیق نگهداری را نشان میدهند و مشخص میکنند کدام مرجع قوی مانع آزادسازی شیء میشود. پروفایلگیری منظم حافظه باید بخشی از pipeline CI/CD هر پروژه موبایل باشد.
Swift و Kotlin از مکانیزمهای اساساً متفاوت مدیریت حافظه استفاده میکنند، اما مفهوم مرجع قوی در هر دو وجود دارد. در Swift از ARC با آزادسازی همزمان در retain count = 0 استفاده میشود. در Kotlin از GC ردیاب استفاده میشود که اشیاء غیرقابل دسترسی را به صورت ناهمزمان پاک میکند.
| پارامتر | Swift (ARC) | Kotlin (JVM GC) |
|---|---|---|
| مکانیزم | شمارش مراجع (retain count) | ردیابی دسترسیپذیری (GC Roots) |
| آزادسازی | همزمان (هنگام صفر شدن شمارنده) | ناهمزمان (چرخه GC) |
| Retain cycle | به طور خودکار تشخیص داده نمیشود | GC میتواند تشخیص دهد، اما نه فوراً |
| Weak ref | weak (صفر شدن خودکار) | WeakReference (بررسی دستی) |
تفاوت عملی اصلی: در Swift retain cycle — یک نشت تضمینی است. در Kotlin GC ممکن است چرخه را در صورت غیرقابل دسترسی بودن اشیاء از ریشه بشکند، اما عمر اشیاء نشتی غیرقابل پیشبینی باقی میماند. بنابراین در هر دو زبان بهترین استراتژی — اجتناب از چرخههای مراجع قوی در مرحله طراحی است.
برای Swift در الگوهای delegate و بستارها از weak استفاده کنید. برای Kotlin — WeakReference یا کامپوننتهای Lifecycle-aware که به طور خودکار مراجع را هنگام نابودی مالک پاک میکنند. در هر دو رویکرد هدف یکسان است — حذف مراجع قوی در جاهایی که زنجیره نگهداری ناگسستنی ایجاد میکنند.
سوالات متداول
Strong Reference retain count شیء را افزایش میدهد و تا زمانی که مرجع وجود دارد از آزادسازی آن جلوگیری میکند. Weak Reference retain count را تغییر نمیدهد و هنگامی که شیء از حافظه حذف میشود به طور خودکار صفر میشود. مراجع قوی برای مالکیت استفاده میشوند، مراجع ضعیف — برای اتصالات بازگشتی و delegateها.
Retain cycle — یک بنبست متقابل است که در آن دو شیء یکدیگر را با مراجع قوی نگه میدارند. retain count آنها هرگز به صفر نمیرسد، حافظه آزاد نمیشود. این منجر به نشت حافظه میشود: اشیاء برای همیشه در heap باقی میمانند، برنامه منابع بیشتری مصرف میکند و در نهایت با OutOfMemory سقوط میکند.
از Instruments Leaks از Xcode استفاده کنید — پروفایلگیری را با قالب Leaks شروع کنید، سناریو را در برنامه اجرا کنید و نشانگرهای نشت را بررسی کنید. برای تشخیص دقیق به برگه Cycles & Roots بروید — گراف مراجع قوی متقابل را نشان میدهد که یک چرخه ناگسستنی تشکیل میدهند.
Unowned را زمانی استفاده کنید که عمر شیء فرزند قطعاً از عمر والد تجاوز نمیکند — مثلاً هنگام اتصال شیء به یک محدوده به شدت تعریفشده. در صورت تردید از Weak استفاده کنید، زیرا مراجعه به unowned آزاد شده باعث crash برنامه میشود.
به طور غیرمستقیم — بله. هر retain و release در ARC یک عملیات اتمی با سربار است. با تعداد زیاد اشیاء در چرخهها، این میتواند بر عملکرد تأثیر بگذارد. اما مشکل اصلی — سرعت کار ARC نیست، بلکه نشت حافظه ناشی از انتخاب نادرست نوع مرجع است.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید