Strong Reference (المرجع القوي) « هي آلية قياسية لإدارة الذاكرة حيث يبقى الكائن في الذاكرة طالما يوجد مرجع نشط واحد على الأقل يشير إليه. على عكس المراجع الضعيفة، المرجع القوي يزيد عداد مراجع الكائن ويمنع تحريره التلقائي. وفقاً لوثائق Apple Developer Documentation، يدير ARC تلقائياً دورة حياة الكائنات في Swift و Objective-C. فهم عمل المراجع القوية أمر بالغ الأهمية لمنع تسرب الذاكرة والتبعيات الدائرية في التطبيقات المحمولة.
النقاط الرئيسية
Strong Reference هو نوع من المراجع إلى كائن يمنع تدميره بواسطة جامع القمامة أو نظام إدارة الذاكرة. طالما يوجد مرجع قوي واحد على الأقل للكائن، لا يتم تحرير ذاكرته. هذه هي الآلية الأساسية التي تقوم عليها ARC في Swift و Objective-C وجمع القمامة في Java و Kotlin.
مفهوم المرجع القوي أساسي لجميع اللغات ذات إدارة الذاكرة التلقائية. في الأنظمة التي تستخدم ARC، كل مرجع قوي يزيد عداد مراجع الكائن. عندما يصل العداد إلى الصفر، يتم تحرير الكائن فوراً. في Java و Kotlin مع جمع القمامة، يضمن المرجع القوي أن الكائن قابل للوصول ولن يتم جمعه بواسطة GC.
وفقاً لـ WWDC 2021، حوالي 35% من تسربات الذاكرة في تطبيقات iOS مرتبطة بالاستخدام غير الصحيح للمراجع القوية ودورات الاحتفاظ. في تطوير Android، تسرب الذاكرة عبر المراجع القوية الضمنية في closures و callbacks هو ثاني أكثر أسباب مشاكل الذاكرة شيوعاً بعد Context Leak.
للعمل بفعالية مع الذاكرة، تحتاج إلى فهم الفرق بين المراجع strong و weak و unowned واختيار نوع المرجع المناسب بناءً على الملكية وعمر الكائنات.
قبل ARC، كان المطورون يستدعون retain و release يدوياً لكل كائن، مما أدى إلى العديد من الأخطاء. ARC، الذي قدمته Apple في 2011 مع LLVM 3.0، أتمت هذه العملية من خلال تحليل رسم بياني للملكية في وقت الترجمة. المترجم نفسه يقوم بإدراج استدعاءات retain و release و autorelease في الأماكن المناسبة.
وفقاً لـ Clang Static Analyzer، أدى إدخال ARC إلى تقليل الأخطاء المتعلقة بالذاكرة في تطبيقات iOS بنسبة 70%. بالنسبة للمطورين، هذا يعني أن إدارة الذاكرة أصبحت أكثر أماناً، ولكن في الوقت نفسه ظهرت الحاجة إلى فهم كيفية عمل المراجع القوية تحت الغطاء — لتجنب retain cycles.
في Kotlin و Java، يلعب جامع القمامة دور ARC، لكن مبدأ المرجع القوي يبقى كما هو: GC Roots هي نقاط الدخول التي يتم من خلالها الاحتفاظ بالكائنات بواسطة مراجع قوية. طالما أن الكائن قابل للوصول عبر سلسلة من المراجع القوية من GC Root، لن يتم جمعه.
ARC (Automatic Reference Counting) يعمل عن طريق عد المراجع لكل كائن في heap. عند إنشاء مرجع قوي جديد لكائن، يزداد العداد (retain). عند تدمير المرجع أو استبداله، ينخفض العداد (release). عندما يصل العداد إلى الصفر، يتم إزالة الكائن فوراً من الذاكرة.
لنفكر في مثال بلغة Swift. عند إنشاء مثيل لفئة، يقوم ARC بتخصيص الذاكرة وتحديد retain count إلى 1. كل تعيين جديد لمتغير آخر يزيد العداد. عندما يخرج المتغير من النطاق، ينخفض العداد:
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 يخرج من النطاق، retain count = 1
}
}
في هذا الكود، يضمن ARC أن كائن User يبقى في الذاكرة طالما يوجد مرجع قوي واحد على الأقل يشير إليه. عندما تنتهي دالة loadProfile، يتم تدمير المتغير المحلي user، لكن nameLabel لا يزال يحتفظ بالكائن. سيتم تحرير الذاكرة فقط عندما يتوقف nameLabel عن الوجود أو يتم استبداله.
في Kotlin، يتم توفير سلوك مماثل عبر GC Roots. طالما توجد سلسلة قابلة للتتبع من المراجع القوية من جذر جامع القمامة (مثل حقل ثابت أو مؤشر ترابط نشط)، يبقى الكائن في الذاكرة. الفرق هو أن GC لا يحرر الذاكرة فوراً — يحدث ذلك بشكل غير متزامن بعد تحليل إمكانية الوصول.
في ARC، يحدث التحرير بشكل متزامن عند وصول العداد إلى الصفر. في Swift و Objective-C، تعرف بالضبط متى سيتم إزالة الكائن. في Kotlin و Java، لحظة التحرير غير متوقعة، ولكن يتم تعويض ذلك بمخطط أكثر مرونة لاكتشاف التبعيات الدائرية على مستوى جامع القمامة.
Retain cycle (دورة الاحتفاظ) — هي حالة يكون فيها لكائنين أو أكثر مراجع قوية متبادلة لبعضهما البعض. ونتيجة لذلك، لا ينخفض retain count أبداً إلى الصفر، ولا يتم تحرير الذاكرة حتى بعد أن تصبح الكائنات غير ضرورية للتطبيق.
مثال كلاسيكي: وحدة تحكم عرض رئيسية تحمل كائناً فرعياً بمرجع قوي، وهو بدوره يحمل الوالد بمرجع قوي. هذا نموذجي في الحالات مع المندوبين و closures وتعبيرات lambda المتداخلة. وفقاً لـ Instruments Leaks، تشكل retain cycles ما يصل إلى 60% من جميع تسربات الذاكرة في التطبيقات التي تستخدم ARC.
class ParentViewController: UIViewController {
var child: ChildViewController?
func setupChild() {
child = ChildViewController()
// retain cycle: الوالد يحمل الطفل، الطفل يحمل الوالد عبر closure
child?.onEvent = {
self.handleEvent()
}
}
func handleEvent() {}
}
المشكلة هنا هي أن closure onEvent تلتقط self (ParentViewController) بمرجع قوي، و ParentViewController نفسه يحمل child بمرجع قوي. لن يتم تحرير كلا الكائنين أبداً. الحل هو استخدام weak self في closure لكسر الدورة.
في Kotlin، تنشأ دورات مماثلة عند استخدام lambdas التي تلتقط كائنات خارجية. يمكن لجامع القمامة في JVM اكتشاف مثل هذه الدورات بمرور الوقت، ولكن فقط إذا كانت الكائنات غير قابلة للوصول من GC Roots. إذا كانت الدورة مرتبطة بمؤشر ترابط نشط أو سياق UI، يبقى التسرب طوال عمر التطبيق.
فهم الفرق بين أنواع المراجع هو مفتاح إدارة الذاكرة الآمنة. Strong Reference يزيد retain count. Weak Reference لا يزيد retain count ويصبح nil تلقائياً عند تحرير الكائن. Unowned Reference أيضاً لا يزيد retain count ولكنه لا يصبح صفراً — الوصول إليه بعد التحرير يسبب تعطل التطبيق.
| نوع المرجع | Retain count | الأمان | متى الاستخدام |
|---|---|---|---|
| Strong | +1 | آمن (افتراضي) | ملكية الكائن، علاقة الوالد → الطفل |
| Weak | لا يتغير | إلغاء تلقائي (آمن) | المندوبون، callbacks، المراجع العكسية |
| Unowned | لا يتغير | خطر تعطل عند الوصول المتأخر | عندما يعيش الكائن لفترة أطول من المالك |
اختيار نوع المرجع تمليه علاقة الملكية. إذا كان الكائن B جزءاً من A ولا يمكن أن يوجد بدونه — استخدم Strong. إذا كان B يمكن أن يوجد بشكل مستقل ويشير إلى A للإشعارات — استخدم Weak. Unowned نادر الاستخدام — فقط عندما لا يتجاوز عمر الكائن الفرعي عمر الأصل.
Apple Developer Documentation توصي: افتراضياً، استخدم strong لجميع علاقات الملكية. إذا كنت بحاجة لتجنب retain cycle — حدد أي مرجع يجب أن يكون ضعيفاً. عادة ما يكون هذا هو المرجع العكسي في التسلسل الهرمي (الطفل → الوالد). في Kotlin، دور مشابه يؤديه WeakReference من java.lang.ref، والذي يستخدم للـ caches وأنماط observer.
اكتشاف retain cycles هو الخطوة الأولى. الثانية هي إزالتها بشكل صحيح. الأداة الرئيسية لكسر دورات المراجع القوية هي استبدال أحد المراجع بـ weak أو unowned. في اللغات مع جمع القمامة، يُستخدم WeakReference مع التحقق اليدوي من null قبل كل وصول.
في Swift و Objective-C، الإصلاح الأكثر شيوعاً هو إضافة [weak self] في closures. هذا يضمن أن closure لا يحتفظ بالكائن بعد تحريره. في Kotlin، تُستخدم أغلفة 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 لا يتم الاحتفاظ به بواسطة closure بعد أن لم يعد مطلوباً. إذا تم تحرير self قبل اكتمال الطلب — guard let self else { return } يخرج من closure دون استدعاء completion.
لتشخيص retain cycles، استخدم Instruments Leaks لنظام iOS أو Android Profiler + LeakCanary لنظام Android. تظهر هذه الأدوات الرسم البياني الدقيق للاحتفاظ وتشير إلى أي مرجع قوي يمنع تحرير الكائن. يجب أن يكون التنميط المنتظم للذاكرة جزءاً من خط أنابيب 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 (إلغاء تلقائي) | WeakReference (تحقق يدوي) |
الفرق العملي الرئيسي: في Swift، retain cycle هو تسرب مضمون. في Kotlin، يمكن لـ GC كسر الدورة إذا كانت الكائنات غير قابلة للوصول من الجذر، لكن عمر الكائنات المتسربة يظل غير متوقع. لذلك، في كلا اللغتين، أفضل استراتيجية هي تجنب دورات المراجع القوية في مرحلة التصميم.
بالنسبة لـ Swift، استخدم weak في أنماط المندوبين و closures. بالنسبة لـ Kotlin، استخدم WeakReference أو مكونات Lifecycle-aware التي تمسح المراجع تلقائياً عند تدمير المالك. في كلا النهجين، الهدف واحد — القضاء على المراجع القوية حيث تخلق سلسلة احتفاظ غير قابلة للكسر.
الأسئلة الشائعة
Strong Reference يزيد retain count للكائن ويمنع تحريره طالما المرجع موجود. Weak Reference لا يغير retain count ويصبح nil تلقائياً عند إزالة الكائن من الذاكرة. تستخدم المراجع القوية للملكية، والضعيفة للاتصالات العكسية والمندوبين.
Retain cycle — قفل متبادل حيث يحتفظ كائنان ببعضهما البعض بمراجع قوية. لا ينخفض retain count أبداً إلى الصفر، لا يتم تحرير الذاكرة. يؤدي هذا إلى تسرب الذاكرة: تبقى الكائنات في heap إلى الأبد، يستهلك التطبيق المزيد والمزيد من الموارد وفي النهاية ينهار مع OutOfMemory.
استخدم Instruments Leaks من Xcode — شغل التنميط مع قالب Leaks، نفذ سيناريو في التطبيق وتحقق من مؤشرات التسرب. للتشخيص الدقيق، انتقل إلى علامة التبويب Cycles & Roots — تظهر رسم بياني للمراجع القوية المتبادلة التي تشكل دورة غير قابلة للكسر.
Unowned يجب استخدامه عندما يكون عمر الكائن الفرعي مضموناً ألا يتجاوز عمر الأصل — على سبيل المثال، عند ربط كائن بنطاق محدد بدقة. إذا كنت في شك، استخدم Weak، لأن الوصول إلى مرجع unowned محرر يسبب تعطل التطبيق.
بشكل غير مباشر — نعم. كل retain و release في ARC هي عملية ذرية بتكلفة إضافية. مع عدد كبير من الكائنات في دورات، يمكن أن يؤثر ذلك على الأداء. لكن المشكلة الرئيسية ليست سرعة ARC، بل تسرب الذاكرة بسبب نوع مرجع تم اختياره بشكل غير صحيح.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا