إدارة الذاكرة في Swift: كيف يعمل ARC وجامع القمامة

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

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

ابدأ الدورة

لماذا لا يستخدم Swift جامع القمامة التقليدي؟

\n

إذا أتيت إلى عالم Swift من Python أو Java، فمن المحتمل أنك تفاجأت بأن هذه اللغة لا تحتوي على جامع قمامة كلاسيكي (Garbage Collector). بدلاً من ذلك، قامت Apple بتطبيق آلية Automatic Reference Counting (ARC) — العد التلقائي للمراجع. وهذا ليس مجرد نزوة: على الأجهزة المحمولة، كل دورة معالج وكل بايت من الذاكرة له أهميته. جامع القمامة التقليدي (GC) «يجمد» التطبيق أثناء عملية الجمع، وهو ما سيكون كارثة لنظام iOS بمتطلباته لسلاسة الرسوم المتحركة (60 إطارًا في الثانية).

\n

في هذه المقالة، سنشرح كيف تعمل إدارة الذاكرة في Swift فعليًا، وأين تكمن المشاكل مع المراجع الدائرية، وكيفية كتابة كود لا «يتسرب» إلى العدم.

\n\n

ARC: كيف يعمل تحت الغطاء؟

\n

ARC ليس جامع قمامة بالمعنى المألوف. إنه يعمل في مرحلة التجميع: يقوم المترجم بإدراج تعليمات retain و release في الأماكن المناسبة. ولكن هناك فارق بسيط: ARC لا يستطيع معالجة المراجع الدائرية (retain cycles). إذا كان كائنان يحملان بعضهما البعض بمراجع قوية، فلن ينخفض عداد المراجع الخاص بهما أبدًا إلى الصفر — ستتسرب الذاكرة.

\n\n

مثال: فئة بسيطة وعداد المراجع

\n
class 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. المستخدم لديه حساب، والحساب يعرف مالكه. إذا كانت كلتا الخاصيتين عبارة عن مراجع قوية، فستحصل على حلقة مغلقة:

\n\n
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.

\n\n

كيف نعالجها؟ Weak و unowned

\n

الحل هو جعل أحد المراجع ضعيفًا (weak) أو غير مملوك (unowned). الفرق:

\n
    \n
  • weak — دائمًا optional، يصبح تلقائيًا nil عند حذف الكائن. آمن.
  • \n
  • unowned — ليس optional، يفترض أن الكائن يعيش لفترة أطول. إذا تم الوصول إلى كائن محذوف — تعطل التطبيق.
  • \n
\n\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 فهو ثابت

المدونات

توصيات الكتب