كيفية عمل مرجع ضعيف للبروتوكول في Swift الخالص (بدون @objc)؟ دليل شامل مع أمثلة
\n\nتخيل: أنت تكتب تطبيقًا بلغة Swift، وتستخدم نمط المفوض (Delegate)، وفجأة - بام! - كائناتك لا تُدمر، الذاكرة تزداد، والتطبيق يبدأ في التباطؤ. هل هذا مألوف؟ على الأرجح، واجهت دورة احتفاظ (retain cycle) (حلقة من المراجع القوية). مشكلة كلاسيكية عندما يحتفظ المفوض بمرجع قوي على المالك، والمالك يحتفظ بمرجع قوي على المفوض. الحل الذي يتبادر إلى الذهن أولاً هو جعل المرجع إلى البروتوكول ضعيفًا (weak). ولكن هنا، Swift يفاجئنا: لا يمكنك ببساطة تعريف weak var delegate: MyProtocol إذا لم يكن البروتوكول مُعلّمًا بـ @objc. سيعطي المترجم خطأ. لماذا؟ والأهم - كيف نتجاوز هذا؟
في هذه المقالة، سنشرح كيفية عمل مرجع ضعيف للبروتوكول في Swift الخالص بدون @objc. ستتعرف على سبب كون هذه مشكلة، وما هي الحلول المتاحة، وكيفية كتابة كود أنيق وآمن بدون تسرب للذاكرة. هيا بنا!
\n\nلماذا لا يعمل المرجع الضعيف للبروتوكول (weak protocol reference) بشكل افتراضي؟
\n\nدعنا نتذكر كيف يعمل weak في Swift. المرجع الضعيف (weak) يمكن أن يوجد فقط لـ أنواع المراجع (reference types) (الفئات). لماذا؟ لأن كائنات الفئات فقط هي التي تمتلك إدارة ذاكرة عبر ARC (العد التلقائي للمراجع). الهياكل (struct) والتعدادات (enum) هي أنواع قيم (value types)، يتم نسخها، ومفهوم "المرجع الضعيف" لا معنى له بالنسبة لها.
عندما تكتب:
\nweak var delegate: MyProtocol?\nلا يعرف المترجم ما إذا كان MyProtocol سيتم تنفيذه بواسطة فئة أو هيكل أو تعداد. ماذا لو كان هيكلًا؟ عندها لا يمكن تطبيق weak. ومن هنا يأتي الخطأ: «'weak' may only be applied to class and class-bound protocol types».
سابقًا (في Objective-C)، كانت جميع البروتوكولات افتراضيًا يمكن تنفيذها فقط بواسطة الفئات، لذلك كان weak يعمل. في Swift الخالص، البروتوكولات عالمية - وهذا رائع، لكنه يضيف تعقيدات.
الحل رقم 1: بروتوكول مخصص للفئات فقط (class-bound protocol)
\n\nأبسط وأكثر الطرق شيوعًا هو التحديد الصريح بأن البروتوكول يمكن تنفيذه فقط بواسطة الفئات. لهذا، نستخدم الكلمة المفتاحية AnyObject (أو الكلمة القديمة class) في تعريف البروتوكول.
مثال:
\nprotocol MyDelegate: AnyObject {\n func didSomething()\n}\n\nclass ViewController {\n weak var delegate: MyDelegate?\n\n func doSomething() {\n delegate?.didSomething()\n }\n}\n\nclass SomeClass: MyDelegate {\n func didSomething() {\n print("المفوض عمل!")\n }\n}\n\n// الاستخدام\nlet vc = ViewController()\nlet some = SomeClass()\nvc.delegate = some\nvc.doSomething() // سيُطبع: "المفوض عمل!"\n\nكل شيء يعمل. الآن MyDelegate يمكن تنفيذه فقط بواسطة فئة (class)، و weak قانوني. هذا هو النهج الموصى به من Apple للمفوضين.
لماذا هذا جيد؟
\n- \n
- البساطة وسهولة القراءة. \n
- دعم كامل لـ ARC. \n
- لا حاجة لـ
@objc. \n
عيوب؟
\n- \n
- تفقد القدرة على تنفيذ البروتوكول بواسطة هيكل. ولكن بالنسبة للمفوضين، هذا عادة غير مطلوب. \n
الحل رقم 2: استخدام غلاف عام (generic wrapper) (للحالات الاستثنائية)
\n\nماذا لو كنت مضطرًا للسماح للبروتوكول بأن يتم تنفيذه بواسطة الفئات والهياكل معًا، ولكنك تريد مرجعًا ضعيفًا؟ هذا نادر الحدوث، لكنه ممكن. على سبيل المثال، إذا كنت تكتب مكتبة حيث يمكن للمستخدم تمرير فئة وهيكل. عندها يأتي دور الغلاف (wrapper).
\n\nالفكرة: إنشاء هيكل يخزن مرجعًا ضعيفًا على الكائن الذي ينفذ البروتوكول. بالنسبة للفئات - we