لماذا لا يستخدم Swift جامع القمامة التقليدي؟
\nإذا أتيت إلى عالم Swift من Python أو Java، فمن المحتمل أنك تفاجأت بأن هذه اللغة لا تحتوي على جامع قمامة كلاسيكي (Garbage Collector). بدلاً من ذلك، قامت Apple بتطبيق آلية Automatic Reference Counting (ARC) — العد التلقائي للمراجع. وهذا ليس مجرد نزوة: على الأجهزة المحمولة، كل دورة معالج وكل بايت من الذاكرة له أهميته. جامع القمامة التقليدي (GC) «يجمد» التطبيق أثناء عملية الجمع، وهو ما سيكون كارثة لنظام iOS بمتطلباته لسلاسة الرسوم المتحركة (60 إطارًا في الثانية).
\nفي هذه المقالة، سنشرح كيف تعمل إدارة الذاكرة في Swift فعليًا، وأين تكمن المشاكل مع المراجع الدائرية، وكيفية كتابة كود لا «يتسرب» إلى العدم.
\n\nARC: كيف يعمل تحت الغطاء؟
\nARC ليس جامع قمامة بالمعنى المألوف. إنه يعمل في مرحلة التجميع: يقوم المترجم بإدراج تعليمات retain و release في الأماكن المناسبة. ولكن هناك فارق بسيط: ARC لا يستطيع معالجة المراجع الدائرية (retain cycles). إذا كان كائنان يحملان بعضهما البعض بمراجع قوية، فلن ينخفض عداد المراجع الخاص بهما أبدًا إلى الصفر — ستتسرب الذاكرة.
مثال: فئة بسيطة وعداد المراجع
\nclass Person {\n let name: String\n init(name: String) {\n self.name = name\n print("\\(name) ظهر في الذاكرة")\n }\n deinit {\n print("\\(name) اختفى من الذاكرة")\n }\n}\n\nvar person1: Person? = Person(name: "أليس") // retain count = 1\nvar person2 = person1 // retain count = 2\nperson1 = nil // retain count = 1\nperson2 = nil // retain count = 0 → deinit\n\nفي وحدة التحكم سترى: «أليس ظهر في الذاكرة» وبعد فترة — «أليس اختفى من الذاكرة». كل شيء عادل.
\n\nالمراجع الدائرية: العدو الرئيسي للذاكرة
\nتخيل: لديك User و Account. المستخدم لديه حساب، والحساب يعرف مالكه. إذا كانت كلتا الخاصيتين عبارة عن مراجع قوية، فستحصل على حلقة مغلقة:
class User {\n let name: String\n var account: Account?\n init(name: String) { self.name = name }\n deinit { print("User \\(name) تم حذفه") }\n}\n\nclass Account {\n let number: String\n var owner: User?\n init(number: String) { self.number = number }\n deinit { print("Account \\(number) تم حذفه") }\n}\n\nvar user: User? = User(name: "بوريس")\nvar account: Account? = Account(number: "12345")\nuser?.account = account\naccount?.owner = user\n\nuser = nil // User لم يتم حذفه! account?.owner لا يزال يحمل المرجع\naccount = nil // Account لم يتم حذفه! user?.account يحمل المرجع\n// تسرب للذاكرة!\n\nلن تظهر أي رسالة deinit في وحدة التحكم. الكائنان عالقان في الذاكرة إلى الأبد. هذه هي retain cycle — مشكلة كلاسيكية في إدارة الذاكرة في Swift.
كيف نعالجها؟ Weak و unowned
\nالحل هو جعل أحد المراجع ضعيفًا (weak) أو غير مملوك (unowned). الفرق:
- \n
- weak — دائمًا optional، يصبح تلقائيًا
nilعند حذف الكائن. آمن. \n - unowned — ليس optional، يفترض أن الكائن يعيش لفترة أطول. إذا تم الوصول إلى كائن محذوف — تعطل التطبيق. \n
class Account {\n let number: String\n weak var owner: User? // ← مرجع ضعيف\n init(number: String) { self.number = number }\n deinit { print("Account \\(number) تم حذفه") }\n}\n// الآن user = nil سيحرر User، و account?.owner سيصبح nil\n\n\nجامع القمامة vs ARC: أيهما أسرع؟
\nلنكن صادقين: ARC ليس «جامع قمامة» بالمعنى الكلاسيكي. GC الحقيقي (كما في Java أو Go أو Python) يعمل في وقت التشغيل: يقوم بمسح الكومة بشكل دوري، ويجد الكائنات غير القابلة للوصول ويحررها. وهذا يتطلب توقفات (stop-the-world).
\nأما ARC فهو ثابت