Weak Reference — این چیست، نحو و کاربرد در توسعه موبایل

نویسنده: IT Sectr منتشر شده: 2026-03-30 زمان مطالعه: 9 دقیقه

Weak Reference (ارجاع ضعیف) — ارجاعی به یک شیء است که شمارنده نگهداشت آن را در ARC افزایش نمی‌دهد. بر اساس Apple Swift Language Guide, 2026، ارجاعات weak با کلمه کلیدی weak اعلام می‌شوند و همیشه نوع اختیاری دارند. هنگامی که شیء آزاد می‌شود، تمام ارجاعات weak به آن به طور خودکار به nil تنظیم می‌شوند که از اشاره‌گرهای آویزان جلوگیری می‌کند و ارجاعات ضعیف را به مکانیزمی امن برای شکستن retain cycle تبدیل می‌کند.

اصلی

  • Weak Reference — ارجاعی که بر retain count شیء تأثیر نمی‌گذارد؛ پس از آزادسازی شیء صفر می‌شود
  • اعلام — کلمه کلیدی weak قبل از var؛ نوع همیشه اختیاری (?)
  • کاربرد — delegate‌ها، closure‌ها، روابط parent-child برای شکستن retain cycle
  • امنیت — تنظیم خودکار به nil پس از تخصیص‌زدایی شیء (zeroing weak)
  • تفاوت با unowned — weak صفر می‌شود و امن است، unowned صفر نمی‌شود و نیاز به تضمین عمر دارد

Weak Reference چیست؟

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 در Swift و Objective-C

نحو اعلام ارجاعات weak را در هر دو زبان اکوسیستم Apple بررسی می‌کنیم. علیرغم runtime مشترک، نحو متفاوت است، اما معناشناسی یکسان است.

Swift

در Swift ارجاعات weak با کلمه کلیدی weak قبل از var اعلام می‌شوند. نوع باید همیشه اختیاری باشد (Type?)، زیرا ارجاع ممکن است در هر لحظه صفر شود. ثابت‌ها (let) نمی‌توانند weak باشند — فقط متغیرها.

swift
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

در Objective-C ویژگی‌های weak از طریق صفت __weak یا اصلاح‌کننده weak در property اعلام می‌شوند:

objective-c
// 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‌ها (Delegate pattern)

Delegate — سناریوی اصلی برای weak. شیء مالک (مثلاً UITableView) ارجاع strong به خود نگه می‌دارد، و delegate (UIViewController) نباید مالک جدول باشد. Apple SDK تضمین می‌کند که همه delegate و dataSource ها weak هستند. برای پروتکل‌های خود همیشه از weak var delegate استفاده کنید.

Parent-Child با بازخورد

زمانی که شیء فرزند باید به والد ارجاع دهد (مثلاً ChildViewController برای دسترسی به هماهنگ‌کننده)، از ارجاع weak استفاده کنید. والد مالک فرزند است (strong)، فرزند والد را مشاهده می‌کند (weak) — retain cycle منتفی است.

closure‌های ناهمگام

Capture list [weak self] — روش استاندارد برای اجتناب از retain cycle در closure‌هایی که به عنوان ویژگی‌های کلاس ذخیره می‌شوند. اگر self ممکن است قبل از تکمیل closure آزاد شود — weak self اجباری است.

سناریوWeakStrong
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 vs Unowned: مقایسه و سناریوها

هم weak و هم unowned retain count را افزایش نمی‌دهند، اما رفتار آنها پس از تخصیص‌زدایی شیء متفاوت است. انتخاب بین آنها موضوع ضمانت‌های عمر است.

تفاوت‌ها

Weak: به طور خودکار صفر می‌شود (nil)، نوع همیشه optional است، قبل از استفاده نیاز به unwrap دارد. امن است — دسترسی به nil باعث crash نمی‌شود.

Unowned: صفر نمی‌شود، نوع non-optional است. اگر شیء آزاد شده باشد، ارجاع unowned به یک اشاره‌گر آویزان تبدیل می‌شود — دسترسی به آن باعث runtime crash می‌شود. Unowned فرض می‌کند که شیء کمتر از طرف ارجاع‌دهنده عمر نمی‌کند.

چه زمانی weak انتخاب کنیم

Weak را انتخاب کنید اگر: شیء ممکن است در هر لحظه تخصیص‌زدایی شود (delegate پس از بسته شدن صفحه)، عمر شیء را کنترل نمی‌کنید، یا در مورد ضمانت‌ها تردید دارید. Weak — انتخاب امن جهانی است.

چه زمانی unowned انتخاب کنیم

Unowned را انتخاب کنید اگر: شیء تضمیناً نمی‌تواند زودتر از ارجاع‌دهنده آزاد شود (مثلاً Customer → CreditCard، جایی که کارت بدون مشتری وجود ندارد). Unowned API non-optional بدون unwrap می‌دهد که در کد راحت‌تر است.

swift
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) ممکن است در هر لحظه بسته و آزاد شود.

محدودیت‌های ارجاعات weak و دام‌ها

ارجاعات ضعیف — ابزاری قدرتمند هستند، اما محدودیت‌هایی دارند که برای استفاده صحیح در توسعه iOS مهم است.

عملکرد weak

ارجاعات ضعیف از strong کندتر هستند: در هر دسترسی runtime بررسی می‌کند که آیا شیء آزاد شده است (lookup در جدول weak). در اکثر سناریوها تفاوت قابل توجه نیست، اما در حلقه‌های داغ با میلیون‌ها دسترسی weak می‌تواند گلوگاه باشد. برای سناریوهای با بار بالا از strong استفاده کنید و معماری را بازسازی کنید.

Weak برای انواع مقداری قابل اعمال نیست

Struct, enum, tuple — انواع مقداری هستند که در ARC شرکت نمی‌کنند. تلاش برای اعلام weak struct منجر به خطای کامپایل می‌شود. برای ذخیره ارجاع ضعیف به نوع مقداری از wrapper در class-type یا closure استفاده کنید.

Weak در چندنخی

Zeroing weak نخ-ایمن است: اگر شیء در یک نخ آزاد شود، ارجاع weak در همه نخ‌ها به صورت اتمی صفر می‌شود. با این حال فاصله بین خواندن ارجاع weak و استفاده از آن می‌تواند منجر به شرایط مسابقه شود — شیء بین دریافت ارجاع weak و استفاده از آن آزاد می‌شود. راه‌حل: strong-گیری ارجاع ضعیف در یک متغیر محلی.

swift
// شرایط مسابقه با 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 است.

UIView و weak outlet

IBOutlet در Interface Builder باید weak باشند، زیرا سلسله‌مراتب view از قبل ارجاع strong به subview نگه می‌دارد. تکرار ارجاع strong در کنترلر retain cycle ایجاد نمی‌کند، اما اضافی است. ارجاع ضعیف به outlet — توصیه Apple است، اگرچه بسیاری از توسعه‌دهندگان برای سادگی کد از strong استفاده می‌کنند.

سوالات متداول

آیا ارجاع weak می‌تواند به شیء‌ای که هنوز ایجاد نشده اشاره کند؟

خیر، weak فقط می‌تواند به شیء موجود یا nil اشاره کند. هنگام ایجاد یک شیء جدید ابتدا یک ارجاع strong دریافت می‌کنید (از طریق مقداردهنده)، و فقط پس از آن می‌توانید ارجاع ضعیف را اختصاص دهید. weak nil در ابتدا — حالت عادی است.

چرا weak فقط با class-types کار می‌کند؟

Weak بر اساس ARC است که فقط انواع ارجاعی (کلاس‌ها) را مدیریت می‌کند. انواع مقداری (struct, enum) هنگام انتساب کپی می‌شوند و retain count ندارند. برای ارتباط ضعیف انواع مقداری از closure‌ها یا wrapper در class با ویژگی weak استفاده کنید.

Weak چگونه بر عملکرد در حلقه تأثیر می‌گذارد؟

هر دسترسی به ارجاع weak یک lookup در جدول runtime انجام می‌دهد. در حلقه‌ای با میلیون‌ها تکرار این می‌تواند ۲–۵ برابر کندتر از ارجاع strong باشد. برای مسیرهای داغ weak را قبل از حلقه در یک متغیر strong محلی کپی کنید.

چه زمانی ارجاع weak می‌تواند به طور غیرمنتظره nil شود؟

زمانی که تمام ارجاعات strong به شیء از دست بروند — در پایان scope، هنگام تنظیم مجدد ویژگی، هنگام بسته شدن صفحه. در محیط چندنخی این می‌تواند بین دو خط کد رخ دهد. همیشه weak را از طریق guard let یا if let بررسی کنید.

تفاوت weak با __weak در Objective-C چیست؟

از نظر معنایی یکسان است: هر دو zeroing weak را فراهم می‌کنند. تفاوت‌ها: Swift نیاز به نوع optional و var دارد، Objective-C از اصلاح‌کننده property استفاده می‌کند. Objective-C همچنین از __unsafe_unretained پشتیبانی می‌کند — ارجاع ضعیف بدون zeroing (خطر اشاره‌گر آویزان).

خلاصه

  • Weak Reference — ارجاع غیرمالکانه که retain count را افزایش نمی‌دهد و در تخصیص‌زدایی به طور خودکار صفر می‌شود
  • نحوweak var + نوع optional؛ فقط class-types و پروتکل‌های AnyObject
  • Zeroing weak — runtime تمام ارجاعات weak به شیء آزاد شده را صفر می‌کند و از اشاره‌گرهای آویزان جلوگیری می‌کند
  • سناریوها — delegate‌ها، parent-child با بازخورد، closure‌های ناهمگام ([weak self])
  • Weak vs Unowned — weak صفر می‌شود (ایمن)، unowned صفر نمی‌شود (خطر crash، اما non-optional)
  • عملکرد — weak به دلیل lookup در جدول runtime از strong کندتر است؛ برای مسیرهای داغ به strong کپی کنید
  • توصیه — اگر از ضمانت‌های عمر مطمئن نیستید — weak را انتخاب کنید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید