Weak Reference — ما هو، تركيبته واستخدامه في تطوير التطبيقات

المؤلف: IT Sectr نُشر: 2026-03-30 وقت القراءة: 9 دق

Weak Reference (مرجع ضعيف) هو مرجع إلى كائن لا يزيد عداد الاحتفاظ به في ARC. وفقًا لـ Apple Swift Language Guide, 2026، يتم تعريف المراجع الضعيفة بالكلمة المفتاحية weak ودائمًا ما تكون اختيارية. عندما يتم تحرير الكائن، يتم تعيين جميع المراجع الضعيفة إليه تلقائيًا إلى nil، مما يمنع المؤشرات المعلقة ويجعل المراجع الضعيفة آلية آمنة لكسر دورات الاحتفاظ.

الرئيسية

  • Weak Reference — مرجع لا يؤثر على retain count للكائن؛ يصبح nil عند تحرير الكائن
  • التعريف — الكلمة المفتاحية weak قبل var؛ النوع دائمًا اختياري (?)
  • الاستخدام — المفوّضون، الإغلاقات، علاقات الأب-الابن لكسر دورات الاحتفاظ
  • الأمان — التعيين التلقائي إلى nil بعد تحرير الكائن (zeroing weak)
  • الفرق عن unowned — weak يصبح nil وهو آمن، unowned لا يصبح nil ويتطلب ضمانات عمر

ما هو Weak Reference؟

Weak Reference هو مرجع غير مالك لكائن في ARC (Automatic Reference Counting). على عكس المرجع القوي، الذي يزيد retain count للكائن ويضمن عمره، يسمح المرجع الضعيف بتحرير الكائن حتى لو كان لا يزال مرجعًا إليه. بعد التحرير، يتم تعيين المرجع الضعيف تلقائيًا إلى nil — وهذا ما يسمى zeroing weak.

Zeroing weak هي ميزة رئيسية في runtime لكل من Swift و Objective-C. عندما يصل عداد مراجع الكائن إلى الصفر ويتم تحرير الكائن، يقوم runtime بمراجعة جميع المراجع الضعيفة لهذا الكائن (المخزنة في جدول ضعيف خاص) وتعيينها إلى nil. هذا يضمن أن الوصول إلى الذاكرة المحررة (use-after-free) مستحيل عبر المراجع الضعيفة — أي قراءة ترجع nil.

وفقًا لـ Apple WWDC 2012 Session 406، قامت المراجع الضعيفة zeroing بالقضاء على فئة كاملة من أخطاء الأعطال المتعلقة بالمؤشرات المعلقة (dangling pointers)، التي كانت شائعة في إدارة الذاكرة اليدوية (MRR). في MRR، كانت المراجع الضعيفة موجودة فقط كـ __unsafe_unretained — لم تكن تُصفّر، وكان الوصول إلى كائن محرر يؤدي إلى EXC_BAD_ACCESS.

تركيبة weak في Swift و Objective-C

دعنا ننظر إلى تركيب تعريف المراجع الضعيفة في كلتا لغتي نظام Apple البيئي. على الرغم من runtime المشترك، تختلف التركيبة، لكن الدلالات متطابقة.

Swift

في Swift، يتم تعريف المراجع الضعيفة بالكلمة المفتاحية weak قبل var. يجب أن يكون النوع دائمًا اختياريًا (Type?)، لأن المرجع يمكن أن يصبح nil في أي وقت. الثوابت (let) لا يمكن أن تكون weak — فقط المتغيرات.

swift
class ViewController: UIViewController {
    // weak properties: only var, only optional
    weak var delegate: ViewControllerDelegate?
    weak var parentView: UIView?

    weak var completionHandler: ((Bool) -> Void)?  // ⚠️ الإغلاقات لا تخزن weak
    // ⬆️ خطأ: weak يمكن تطبيقه فقط على أنواع class، وليس closures
}

مهم: weak قابل للتطبيق فقط على نسخ class (أنواع class)، AnyObject، والبروتوكولات الموروثة من AnyObject. Struct و enum والإغلاقات لا يمكن أن تكون weak — فهي أنواع قيمة ولا تشارك في ARC.

Objective-C

في Objective-C، يتم تعريف الخصائص الضعيفة باستخدام السمة __weak أو المُعدِّل weak في تعريفات الخاصية:

objective-c
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end

// متغير weak محلي
__weak MyObject *weakRef = someStrongObject;

يوفر runtime في Objective-C أيضًا zeroing weak، لكنه additionally يمنع استخدام weak مع هياكل C وبعض كائنات Core Foundation. لهذه، يتم استخدام __unsafe_unretained — بدون zeroing.

متى تستخدم المراجع الضعيفة

المراجع الضعيفة ليست حلاً شاملاً، بل أداة لسيناريوهات محددة. استخدام weak في كل مكان يؤدي إلى تعقيد غير ضروري ويضر بسهولة القراءة. دعنا ننظر إلى سيناريوهات الاستخدام الصحيحة.

المفوّضون (نمط Delegate)

المفوّضون — السيناريو الرئيسي لـ weak. الكائن المالك (مثل UITableView) يحمل مرجعًا قويًا لنفسه، بينما المفوّض (UIViewController) لا يجب أن يمتلك الجدول. يضمن Apple SDK أن جميع المفوّضين ومصادر البيانات هم weak. للبروتوكولات الخاصة بك، استخدم دائمًا weak var delegate.

أب-ابن بمرجع عكسي

عندما يحتاج كائن ابن إلى الإشارة إلى والده (مثل ChildViewController الوصول إلى المنسق)، استخدم مرجعًا ضعيفًا. الأب يمتلك الابن (strong)، الابن يراقب الأب (weak) — دورة الاحتفاظ مستبعدة.

الإغلاقات غير المتزامنة

Capture list [weak self] — الطريقة القياسية لتجنب دورات الاحتفاظ في الإغلاقات المخزنة كخصائص class. إذا كان self قد يُحرر قبل اكتمال الإغلاق، فإن weak self إلزامي.

السيناريوWeakStrong
مفوّض✅ دائمًا weak❌ دورة احتفاظ
أب ← ابن❌ غير ضروري (الأب يجب أن يمتلك)✅ Strong
ابن ← أب✅ Weak❌ دورة احتفاظ
استدعاء غير متزامن✅ [weak self]❌ خطر دورة احتفاظ
اقتران قوي (owned)❌ unowned✅ Strong

قاعدة عامة: إذا كان الكائن A يمتلك B (A → B strong)، فإن B → A يجب أن يكون weak أو unowned. اتجاه المراجع القوية يجب أن يكون دائمًا من المالك إلى التابع.

Weak vs Unowned: مقارنة وسيناريوهات

كل من weak و unowned لا يزيدان retain count، لكنهما يختلفان في السلوك بعد تحرير الكائن. الاختيار بينهما مسألة ضمانات العمر.

الاختلافات

Weak: يصبح nil تلقائيًا، النوع دائمًا اختياري، يتطلب فك تغليف قبل الاستخدام. آمن — الوصول إلى nil لا يسبب عطلًا.

Unowned: لا يصبح nil، النوع غير اختياري. إذا تم تحرير الكائن، يصبح المرجع unowned مؤشرًا معلقًا — الوصول إليه يسبب عطلًا في وقت التشغيل. Unown يفترض أن الكائن يعيش على الأقل بقدر الجهة المرجعية.

متى تختار weak

اختر weak إذا: الكائن قد يُحرر في أي وقت (مفوّض بعد إغلاق الشاشة)، لا تتحكم في عمر الكائن، أو غير متأكد من الضمانات. Weak هو الخيار الآمن العالمي.

متى تختار unowned

اختر unowned إذا: الكائن مضمون ألا يُحرر قبل الكائن المرجع (مثل Customer → CreditCard، حيث البطاقة لا توجد بدون العميل). Unowned يوفر واجهة برمجة غير اختيارية بدون فك تغليف، وهو أكثر ملاءمة في الكود.

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: مفوّض بدون ضمان عمر
protocol NetworkServiceDelegate: AnyObject {
    func didReceiveResponse(data: Data)
}

class NetworkService {
    weak var delegate: NetworkServiceDelegate?  // ✅ weak — المفوّض قد يختفي
}

في المثال، يستخدم Item unowned لأن عنصر الطلب لا يمكن أن يوجد بدون الطلب نفسه — ضمان العمر حديدي. يستخدم NetworkService weak لأن المفوّض (مثل ViewController) قد يُغلق ويُحرر في أي وقت.

قيود المراجع الضعيفة والمزالق

المراجع الضعيفة أداة قوية، لكن لها قيود من المهم فهمها للاستخدام الصحيح في تطوير iOS.

أداء weak

المراجع الضعيفة أبطأ من القوية: في كل وصول، يتحقق runtime مما إذا كان الكائن قد تم تحريره (lookup في الجدول الضعيف). في الغالبية العظمى من السيناريوهات، الفرق غير محسوس، لكن في الحلقات الحرجة بملايين الوصولات، يمكن أن يصبح weak عنق زجاجة. للسيناريوهات عالية الحمل، استخدم strong وأعد تنظيم البنية.

Weak غير قابل للتطبيق على أنواع القيمة

Struct، enum، tuple — أنواع قيمة لا تشارك في ARC. محاولة تعريف weak struct تؤدي إلى خطأ في الترجمة. لتخزين مرجع ضعيف إلى نوع قيمة، استخدم غلافًا في نوع class أو إغلاقًا.

Weak في تعدد الخيوط

Zeroing weak آمن للخيوط: إذا تم تحرير كائن في خيط واحد، يتم تصفير المرجع الضعيف في جميع الخيوط بشكل ذري. ومع ذلك، يمكن أن تؤدي النافذة بين قراءة المرجع الضعيف وإلغاء الإشارة إليه إلى حالة سباق — يتم تحرير الكائن بين الحصول على المرجع الضعيف واستخدامه. الحل: التقاط قوي للمرجع الضعيف في متغير محلي.

swift
// حالة سباق مع weak في تعدد الخيوط
func performAsync() {
    weak var weakSelf = self
    queue.async {
        // ⚠️ weakSelf قد يكون nil بين التحقق والاستخدام
        if weakSelf != nil {
            weakSelf!.doSomething()  // CRASH إذا أصبح nil
        }
    }
}

// ✅ الإصلاح: التقاط قوي أثناء الاستخدام
func performAsyncSafe() {
    queue.async { [weak self] in
        guard let strongSelf = self else { return }
        strongSelf.doSomething()  // strongSelf — مرجع محلي قوي
    }
}

في النسخة الآمنة، يتم التقاط weak self ثم فك تغليفه فورًا في متغير قوي محلي strongSelf. إذا كان self لا يزال حيًا، سيبقى حيًا طوال مدة الكتلة. إذا لم يكن، يتم تفعيل guard ولا يتم تنفيذ الكود. هذا التركيب الاصطلاحي هو النمط القياسي للإغلاقات غير المتزامنة في Swift.

UIView و weak outlets

IBOutlet في Interface Builder يجب أن تكون weak لأن تسلسل الـ view hierarchy يحمل بالفعل مرجعًا قويًا إلى الـ subview. تكرار مرجع قوي في المتحكم لا ينشئ دورة احتفاظ لكنه زائد عن الحاجة. المرجع الضعيف للـ outlet هو توصية Apple، على الرغم من أن العديد من المطورين يستخدمون strong لتبسيط الكود.

الأسئلة الشائعة

هل يمكن أن يشير المرجع الضعيف إلى كائن لم يتم إنشاؤه بعد؟

لا، weak يمكن أن يشير فقط إلى كائن موجود أو nil. عند إنشاء كائن جديد، تحصل أولاً على مرجع قوي (عبر مُهيئ)، وعندها فقط يمكنك تعيين مرجع ضعيف. weak nil في البداية هو حالة طبيعية.

لماذا يعمل weak فقط مع أنواع class؟

Weak يعتمد على ARC، الذي يدير فقط أنواع المرجع (class). أنواع القيمة (struct، enum) تُنسخ عند التعيين وليس لديها retain count. للعلاقات الضعيفة مع أنواع القيمة، استخدم إغلاقات أو أغلفة في class مع خاصية weak.

كيف يؤثر weak على الأداء في حلقة؟

كل وصول إلى مرجع ضعيف يقوم lookup في جدول runtime. في حلقة بملايين التكرارات، يمكن أن يكون هذا أبطأ بمقدار 2–5 مرات من المرجع القوي. للمسارات الحرجة، انسخ weak إلى متغير قوي محلي قبل الحلقة.

متى يمكن أن يصبح المرجع الضعيف nil بشكل غير متوقع؟

عند فقدان جميع المراجع القوية للكائن — في نهاية النطاق، عند إعادة تعيين خاصية، أو عند إغلاق شاشة. في بيئة متعددة الخيوط، يمكن أن يحدث هذا بين سطرين من الكود. تحقق دائمًا من المراجع الضعيفة عبر guard let أو if let.

ما الفرق بين weak و __weak في Objective-C؟

متطابقان دلاليًا: كلاهما يوفر zeroing weak. الاختلافات: Swift يتطلب نوعًا اختياريًا و var، Objective-C يستخدم مُعدِّل property. Objective-C also يدعم __unsafe_unretained — مرجع ضعيف بدون zeroing (خطر مؤشر معلق).

الخلاصة

  • Weak Reference — مرجع غير مالك لا يزيد retain count ويُصفّر تلقائيًا عند التحرير
  • التركيبةweak var + نوع اختياري؛ فقط أنواع class وبروتوكولات AnyObject
  • Zeroing weak — runtime يصفّر جميع المراجع الضعيفة لكائن تم تحريره، مما يمنع المؤشرات المعلقة
  • السيناريوهات — المفوّضون، أب-ابن بمرجع عكسي، إغلاقات غير متزامنة ([weak self])
  • Weak vs Unowned — weak يصبح nil (آمن)، unowned لا يصبح nil (خطر عطل، لكن غير اختياري)
  • الأداء — weak أبطأ من strong بسبب lookup في جدول runtime؛ للمسارات الحرجة، انسخ إلى strong
  • التوصية — إذا لم تكن متأكدًا من ضمانات العمر، اختر weak

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا