لماذا يوجد علامتا مساواة في Swift وماذا تخفيان؟
\n\nهل سبق لك أن كتبت كودًا بلغة Swift وفجأة صادفت العامل الغريب ===؟ أول فكرة: "خطأ مطبعي؟ أم أنه نوع من المساواة الفائقة؟" في الحقيقة، هذا ليس خطأ مطبعيًا، بل آلية قوية تغير منطق المقارنة جذريًا. إذا كنت تعمل مع الكلاسات أو الهياكل أو الإغلاقات، فإن فهم الفرق بين == و === ليس مجرد نظرية، بل ضرورة. بدون ذلك، تخاطر بظهور أخطاء يصعب تتبعها.
في هذه المقالة، سننظر "تحت غطاء" Swift، ونشرح كيفية عمل كلا العاملين، ونتعلم اختيار الأداة الصحيحة لكل مهمة. هل أنت مستعد؟ لنبدأ.
\n\nما هو عامل المساواة ==؟
\n\nالعامل == هو "صديقك" القياسي لمقارنة القيم. يتحقق مما إذا كان كائنان متساويين من حيث محتواهما. ولكن هناك فارق: Swift لا يعرف ماذا يعني "متساوٍ" لنوع البيانات المخصص لديك. لذلك، لاستخدام ==، يجب أن يتوافق نوعك مع البروتوكول Equatable.
هذا ما يبدو عليه الأمر مع الأنواع المضمنة:
\n\nlet a = 5\nlet b = 5\nprint(a == b) // true، لأن القيم متطابقة\n\nlet name1 = "Alice"\nlet name2 = "Alice"\nprint(name1 == name2) // true، السلاسل النصية متطابقة\n\nبالنسبة للكلاسات والهياكل، تحتاج إلى تنفيذ Equatable بنفسك. على سبيل المثال:
struct User {\n let id: Int\n let name: String\n}\n\nextension User: Equatable {\n static func == (lhs: User, rhs: User) -> Bool {\n return lhs.id == rhs.id && lhs.name == rhs.name\n }\n}\n\nlet user1 = User(id: 1, name: "John")\nlet user2 = User(id: 1, name: "John")\nprint(user1 == user2) // true، المحتوى متطابق\n\nلاحظ أن == يقارن القيم وليس المراجع. بالنسبة للهياكل، هذا واضح، أما مع الكلاسات فيبدأ الأمر المثير للاهتمام.
عامل التطابق ===: عندما يكون كائنان هما نفس الشيء
\n\nالعامل === (ثلاث علامات مساواة) لا يتحقق من المحتوى، بل من تطابق المراجع. يجيب على السؤال: "هل يشير متغيران إلى نفس الكائن في الذاكرة؟"
هذا العامل يعمل فقط مع الأنواع المرجعية: الكلاسات والإغلاقات (الإغلاقات هي أيضًا أنواع مرجعية). بالنسبة للهياكل والتعدادات، فهو غير موجود ببساطة - سيصدر المترجم خطأ.
\n\nانظر إلى المثال:
\n\nclass Car {\n var model: String\n init(model: String) {\n self.model = model\n }\n}\n\nlet car1 = Car(model: "Tesla")\nlet car2 = Car(model: "Tesla")\nlet car3 = car1\n\nprint(car1 == car2) // false، إذا لم يتم تنفيذ Equatable\nprint(car1 === car2) // false، كائنان مختلفان\nprint(car1 === car3) // true، نفس المرجع\n\nهنا car1 و car2 هما كائنان مختلفان لهما نفس المحتوى. أما car3 فهو مجرد اسم آخر لنفس الكائن مثل car1. === يوضح ذلك بوضوح.
كيف يعمل هذا تحت الغطاء: الذاكرة والمؤشرات
\n\nلفهم الفرق، تحتاج إلى النظر في الذاكرة. عندما تنشئ مثيلًا لكلاس، يخصص Swift له مساحة في الكومة (heap) ويعيد مؤشرًا إلى هذه المساحة. المتغير يخزن المؤشر وليس الكائن نفسه.
\n\n- \n
==يقارن المحتوى عبر المؤشر: يستدعي تنفيذك لـEquatable، والذي يمكنه النظر إلى حقول الكائن. \n ===يقارن المؤشرات نفسها: إذا كان مؤشران يشيران إلى نفس العنوان في الذاكرة، تكون النتيجةtrue. \n
بالنسبة للهياكل، الأمر أبسط: يتم تخزينها في المكدس (stack) وتُنسخ عند التعيين. لذلك، === لا معنى له بالنسبة لها - هيكلان مختلفان سيكونان دائمًا كائنين مختلفين في الذاكرة، حتى لو كان محتواهما متطابقًا.