[unowned self] في Swift: متى يكون ضروريًا ومتى يكون خطيرًا؟

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

المعضلة الأبدية: هل نضع [unowned self] أم لا؟

\n\n

إذا كنت قد كتبت يومًا بلغة Swift وواجهت الإغلاقات (closures)، فبالتأكيد تساءلت: "هل أحتاج إلى كتابة [unowned self] داخل كل إغلاق أستخدم فيه self؟".

\n\n

هذا أحد أكثر الأسئلة شيوعًا على Stack Overflow (عشرات الآلاف من المشاهدات)، وليس من دون سبب. في عام 2025، مع ظهور ميزات Swift الجديدة (مثل محلل دورة الحياة المحسّن وفحوصات المترجم الأكثر صرامة)، أصبحت الإجابة أكثر دقة.

\n\n

في هذه المقالة، لن نقدم إجابة جافة بـ"نعم" أو "لا". بل سنحلل كيف تعمل إدارة الذاكرة في Swift فعليًا، ومتى يكون [unowned self] هو المنقذ، ومتى يكون قنبلة موقوتة، وما هي أفضل الممارسات التي يجب استخدامها في المشاريع الحديثة.

\n\n

هيا بنا! 🚀

\n\n

نبذة مختصرة عن ARC ودورات الاحتفاظ (للمبتدئين)

\n\n

قبل الحديث عن [unowned self]، دعنا نتذكر كيف يعمل العد التلقائي للمراجع (Automatic Reference Counting - ARC).

\n\n

ARC هي آلية لحساب المراجع. كل كائن في Swift (مثيل من كلاس) لديه عداد مراجع. عندما تنشئ مرجعًا جديدًا للكائن (مثل تعيينه لخاصية أو وضعه في مصفوفة)، يزداد العداد. عندما يختفي المرجع، يقل العداد. وعندما يصل العداد إلى الصفر، يتم تدمير الكائن.

\n\n

تنشأ المشكلة عندما يشير كائنان إلى بعضهما البعض (مرجع دائري). تخيل:

\n\n
class Parent {\n    var child: Child?\n}\n\nclass Child {\n    var parent: Parent? // مرجع قوي يعود إلى Parent\n}
\n\n

إذا قمت بإنشاء زوج من Parent و Child وقمت بتعيين كلا المرجعين، فسيظلان ممسكين ببعضهما البعض إلى الأبد. لن يقوم ARC أبدًا بتصفير عدادهما. وهذا ما يسمى دورة احتفاظ (retain cycle) (دورة من المراجع القوية).

\n\n

الإغلاقات هي أيضًا أنواع مرجعية. عندما يلتقط الإغلاق self (مثلًا لاستدعاء دالة)، فإنه يخزن مرجعًا قويًا للكائن. إذا كان الكائن، بدوره، يخزن مرجعًا قويًا للإغلاق (مثلًا كخاصية)، ينشأ نفس الدورة.

\n\n

Weak vs Unowned: ما الفرق؟

\n\n

لكسر الدورة، نستخدم weak أو unowned.

\n\n

weak self

\n
    \n
  • دائمًا اختياري (optional) (self?).
  • \n
  • المرجع لا يزيد عداد ARC.
  • \n
  • عندما يتم تدمير الكائن، يصبح المرجع weak تلقائيًا nil.
  • \n
  • آمن — تتحقق دائمًا باستخدام guard let self = self else { return }.
  • \n
\n\n

unowned self

\n
    \n
  • غير اختياري (non-optional) (self بدون علامة استفهام).
  • \n
  • المرجع لا يزيد عداد ARC.
  • \n
  • عندما يتم تدمير الكائن، المرجع unowned لا يصبح nil. بل يصبح مرجعًا معلقًا (dangling pointer).
  • \n
  • الوصول إلى هذا المرجع بعد تدمير الكائن يسبب تعطلًا (crash) (EXC_BAD_ACCESS).
  • \n
\n\n

النقطة الأساسية: unowned هو وعد للمترجم: "أضمن أن الكائن سيكون حيًا أثناء تنفيذ هذا الإغلاق". إذا أخطأت، سيتعطل التطبيق.

\n\n

متى تستخدم [unowned self] (ومتى لا)

\n\n

دعنا نحلل سيناريوهات حقيقية من ممارسة تطوير iOS في عام 2025.

\n\n

الحالة 1: [unowned self] مقبول (وحتى مفضل)

\n\n

استخدم [unowned self] عندما تكون متأكدًا تمامًا أن self سيكون حيًا عند تنفيذ الإغلاق. مثال نموذجي هو إغلاق يتم تنفيذه بشكل متزامن داخل دالة الكائن، والكائن مضمون ألا يتم تدميره قبل اكتمال الإغلاق.

\n\n
class DataFetcher {\n    var data: Data?\n    \n    func fetchData() {\n        // هذا الإغلاق يتم تنفيذه بشكل متزامن داخل الدالة\n        let processCl                

المدونات

توصيات الكتب