Weak Reference (ارجاع ضعیف) — ارجاعی به یک شیء است که شمارنده نگهداشت آن را در ARC افزایش نمیدهد. بر اساس Apple Swift Language Guide, 2026، ارجاعات weak با کلمه کلیدی weak اعلام میشوند و همیشه نوع اختیاری دارند. هنگامی که شیء آزاد میشود، تمام ارجاعات weak به آن به طور خودکار به nil تنظیم میشوند که از اشارهگرهای آویزان جلوگیری میکند و ارجاعات ضعیف را به مکانیزمی امن برای شکستن retain cycle تبدیل میکند.
اصلی
weak قبل از var؛ نوع همیشه اختیاری (?)Weak Reference — یک ارجاع غیرمالکانه به یک شیء در ARC (Automatic Reference Counting) است. بر خلاف ارجاع strong که retain count شیء را افزایش میدهد و عمر آن را تضمین میکند، ارجاع weak به شیء اجازه میدهد حتی اگر هنوز به آن ارجاع داده میشود آزاد شود. پس از آزادسازی، ارجاع weak به طور خودکار به nil تنظیم میشود — این zeroing weak نامیده میشود.
Zeroing weak — ویژگی کلیدی runtime Swift و Objective-C است. هنگامی که شمارنده ارجاعات شیء به صفر میرسد و شیء تخصیصزدایی میشود، runtime تمام ارجاعات weak به این شیء (ذخیره شده در جدول weak ویژه) را پیمایش کرده و آنها را به nil تنظیم میکند. این تضمین میکند که دسترسی به حافظه آزاد شده (use-after-free) از طریق ارجاعات weak غیرممکن است — هر خواندنی nil برمیگرداند.
طبق Apple WWDC 2012 Session 406، ارجاعات zeroing weak یک کلاس کامل از باگهای crash مرتبط با اشارهگرهای آویزان (dangling pointers) را که در مدیریت دستی حافظه (MRR) رایج بودند حذف کردند. در MRR ارجاعات ضعیف فقط به صورت __unsafe_unretained وجود داشتند — آنها صفر نمیشدند و دسترسی به شیء آزاد شده منجر به EXC_BAD_ACCESS میشد.
نحو اعلام ارجاعات weak را در هر دو زبان اکوسیستم Apple بررسی میکنیم. علیرغم runtime مشترک، نحو متفاوت است، اما معناشناسی یکسان است.
در Swift ارجاعات weak با کلمه کلیدی weak قبل از var اعلام میشوند. نوع باید همیشه اختیاری باشد (Type?)، زیرا ارجاع ممکن است در هر لحظه صفر شود. ثابتها (let) نمیتوانند weak باشند — فقط متغیرها.
class ViewController: UIViewController {
// ویژگیهای weak: فقط var، فقط optional
weak var delegate: ViewControllerDelegate?
weak var parentView: UIView?
weak var completionHandler: ((Bool) -> Void)? // ⚠️ closureها weak را ذخیره نمیکنند
// ⬆️ خطا: weak فقط برای class-types قابل اعمال است، نه برای closure
}
مهم: weak فقط برای نمونههای کلاس (class-types)، AnyObject و پروتکلهای به ارث برده از AnyObject قابل اعمال است. Struct، enum و closure نمیتوانند weak باشند — آنها انواع مقداری هستند و در ARC شرکت نمیکنند.
در Objective-C ویژگیهای weak از طریق صفت __weak یا اصلاحکننده weak در property اعلام میشوند:
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end
// متغیر weak محلی
__weak MyObject *weakRef = someStrongObject;
Objective-C runtime نیز zeroing weak را فراهم میکند، اما علاوه بر این استفاده از weak را با ساختارهای C و برخی اشیاء Core Foundation مسدود میکند. برای آنها از __unsafe_unretained استفاده میشود — بدون zeroing.
ارجاعات ضعیف — راهحل جهانی نیستند، بلکه ابزاری برای سناریوهای خاص هستند. استفاده از weak در همه جا منجر به پیچیدگی بیش از حد میشود و خوانایی را کاهش میدهد. بیایید سناریوهای صحیح استفاده را بررسی کنیم.
Delegate — سناریوی اصلی برای weak. شیء مالک (مثلاً UITableView) ارجاع strong به خود نگه میدارد، و delegate (UIViewController) نباید مالک جدول باشد. Apple SDK تضمین میکند که همه delegate و dataSource ها weak هستند. برای پروتکلهای خود همیشه از weak var delegate استفاده کنید.
زمانی که شیء فرزند باید به والد ارجاع دهد (مثلاً ChildViewController برای دسترسی به هماهنگکننده)، از ارجاع weak استفاده کنید. والد مالک فرزند است (strong)، فرزند والد را مشاهده میکند (weak) — retain cycle منتفی است.
Capture list [weak self] — روش استاندارد برای اجتناب از retain cycle در closureهایی که به عنوان ویژگیهای کلاس ذخیره میشوند. اگر self ممکن است قبل از تکمیل closure آزاد شود — weak self اجباری است.
| سناریو | Weak | Strong |
|---|---|---|
| Delegate | ✅ همیشه weak | ❌ Retain cycle |
| Parent → Child | ❌ نیاز نیست (والد باید مالک باشد) | ✅ Strong |
| Child → Parent | ✅ Weak | ❌ Retain cycle |
| callback ناهمگام | ✅ [weak self] | ❌ خطر retain cycle |
| ارتباط قوی (owned) | ❌ unowned | ✅ Strong |
قاعده کلی: اگر شیء A مالک B است (A → B strong)، آنگاه B → A باید weak یا unowned باشد. جهت ارجاعات strong همیشه باید از مالک به زیردست باشد.
هم weak و هم unowned retain count را افزایش نمیدهند، اما رفتار آنها پس از تخصیصزدایی شیء متفاوت است. انتخاب بین آنها موضوع ضمانتهای عمر است.
Weak: به طور خودکار صفر میشود (nil)، نوع همیشه optional است، قبل از استفاده نیاز به unwrap دارد. امن است — دسترسی به nil باعث crash نمیشود.
Unowned: صفر نمیشود، نوع non-optional است. اگر شیء آزاد شده باشد، ارجاع unowned به یک اشارهگر آویزان تبدیل میشود — دسترسی به آن باعث runtime crash میشود. Unowned فرض میکند که شیء کمتر از طرف ارجاعدهنده عمر نمیکند.
Weak را انتخاب کنید اگر: شیء ممکن است در هر لحظه تخصیصزدایی شود (delegate پس از بسته شدن صفحه)، عمر شیء را کنترل نمیکنید، یا در مورد ضمانتها تردید دارید. Weak — انتخاب امن جهانی است.
Unowned را انتخاب کنید اگر: شیء تضمیناً نمیتواند زودتر از ارجاعدهنده آزاد شود (مثلاً Customer → CreditCard، جایی که کارت بدون مشتری وجود ندارد). Unowned API non-optional بدون unwrap میدهد که در کد راحتتر است.
class Order {
let id: Int
var items: [Item] = []
init(id: Int) { self.id = id }
// ارتباط قوی: Order مالک Item است
func addItem(name: String) {
let item = Item(name: name, order: self)
items.append(item)
}
}
class Item {
let name: String
unowned let order: Order // ✅ unowned — Item بدون Order زندگی نمیکند
init(name: String, order: Order) {
self.name = name
self.order = order
}
}
// مثال با weak: delegate بدون ضمانت عمر
protocol NetworkServiceDelegate: AnyObject {
func didReceiveResponse(data: Data)
}
class NetworkService {
weak var delegate: NetworkServiceDelegate? // ✅ weak — delegate میتواند برود
}
در مثال Item از unowned استفاده میکند، زیرا عنصر سفارش بدون خود سفارش نمیتواند وجود داشته باشد — ضمانت عمر آهنین است. NetworkService از weak استفاده میکند، زیرا delegate (مثلاً ViewController) ممکن است در هر لحظه بسته و آزاد شود.
ارجاعات ضعیف — ابزاری قدرتمند هستند، اما محدودیتهایی دارند که برای استفاده صحیح در توسعه iOS مهم است.
ارجاعات ضعیف از strong کندتر هستند: در هر دسترسی runtime بررسی میکند که آیا شیء آزاد شده است (lookup در جدول weak). در اکثر سناریوها تفاوت قابل توجه نیست، اما در حلقههای داغ با میلیونها دسترسی weak میتواند گلوگاه باشد. برای سناریوهای با بار بالا از strong استفاده کنید و معماری را بازسازی کنید.
Struct, enum, tuple — انواع مقداری هستند که در ARC شرکت نمیکنند. تلاش برای اعلام weak struct منجر به خطای کامپایل میشود. برای ذخیره ارجاع ضعیف به نوع مقداری از wrapper در class-type یا closure استفاده کنید.
Zeroing weak نخ-ایمن است: اگر شیء در یک نخ آزاد شود، ارجاع weak در همه نخها به صورت اتمی صفر میشود. با این حال فاصله بین خواندن ارجاع weak و استفاده از آن میتواند منجر به شرایط مسابقه شود — شیء بین دریافت ارجاع weak و استفاده از آن آزاد میشود. راهحل: strong-گیری ارجاع ضعیف در یک متغیر محلی.
// شرایط مسابقه با weak در چندنخی
func performAsync() {
weak var weakSelf = self
queue.async {
// ⚠️ weakSelf ممکن است بین بررسی و استفاده nil باشد
if weakSelf != nil {
weakSelf!.doSomething() // CRASH اگر nil شد
}
}
}
// ✅ رفع: strong-گیری برای مدت استفاده
func performAsyncSafe() {
queue.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.doSomething() // strongSelf — ارجاع strong محلی
}
}
در نسخه امن weak self گرفته میشود، سپس بلافاصله در متغیر strong محلی strongSelf باز میشود. اگر self هنوز زنده است — تا زمان اجرای بلاک زنده میماند. اگر نه — guard فعال میشود و کد اجرا نمیشود. این idiom — الگوی استاندارد برای closureهای ناهمگام در Swift است.
IBOutlet در Interface Builder باید weak باشند، زیرا سلسلهمراتب view از قبل ارجاع strong به subview نگه میدارد. تکرار ارجاع strong در کنترلر retain cycle ایجاد نمیکند، اما اضافی است. ارجاع ضعیف به outlet — توصیه Apple است، اگرچه بسیاری از توسعهدهندگان برای سادگی کد از strong استفاده میکنند.
سوالات متداول
خیر، weak فقط میتواند به شیء موجود یا nil اشاره کند. هنگام ایجاد یک شیء جدید ابتدا یک ارجاع strong دریافت میکنید (از طریق مقداردهنده)، و فقط پس از آن میتوانید ارجاع ضعیف را اختصاص دهید. weak nil در ابتدا — حالت عادی است.
Weak بر اساس ARC است که فقط انواع ارجاعی (کلاسها) را مدیریت میکند. انواع مقداری (struct, enum) هنگام انتساب کپی میشوند و retain count ندارند. برای ارتباط ضعیف انواع مقداری از closureها یا wrapper در class با ویژگی weak استفاده کنید.
هر دسترسی به ارجاع weak یک lookup در جدول runtime انجام میدهد. در حلقهای با میلیونها تکرار این میتواند ۲–۵ برابر کندتر از ارجاع strong باشد. برای مسیرهای داغ weak را قبل از حلقه در یک متغیر strong محلی کپی کنید.
زمانی که تمام ارجاعات strong به شیء از دست بروند — در پایان scope، هنگام تنظیم مجدد ویژگی، هنگام بسته شدن صفحه. در محیط چندنخی این میتواند بین دو خط کد رخ دهد. همیشه weak را از طریق guard let یا if let بررسی کنید.
از نظر معنایی یکسان است: هر دو zeroing weak را فراهم میکنند. تفاوتها: Swift نیاز به نوع optional و var دارد، Objective-C از اصلاحکننده property استفاده میکند. Objective-C همچنین از __unsafe_unretained پشتیبانی میکند — ارجاع ضعیف بدون zeroing (خطر اشارهگر آویزان).
خلاصه
weak var + نوع optional؛ فقط class-types و پروتکلهای AnyObjectما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید