الغامض «some»: لماذا يسأل عنه آلاف المطورين؟
\n\nإذا كنت قد كتبت أي كود في SwiftUI، فمن المؤكد أنك رأيت البنية some View. تبدو بسيطة، لكن المبتدئين غالبًا ما يشعرون بالحيرة: «ما هذا الـ «some»؟ لماذا لا يمكنني كتابة View فقط؟ وإذا أزلت some، هل سيتعطل الكود؟». سؤال «What is the "some" keyword in Swift(UI)?» حصد آلاف المشاهدات على Stack Overflow — وهذا ليس من قبيل الصدفة. خلف هذه الكلمة القصيرة تختبئ فلسفة كاملة للـ Swift الحديث.
في هذه المقالة، سنفهم ما هي الكلمة المفتاحية some، ولماذا ظهرت في اللغة، ولماذا لا يمكن تصور SwiftUI بدونها. ستتعلم كيف ترتبط بالأدوية العامة (Generics) والبروتوكولات و«الأنواع غير الشفافة» (opaque types). والأهم من ذلك، ستفهم كيف تكتب كودًا أنظف وأكثر كفاءة.
\n\nالمشكلة التي يحلها «some»
\n\nتخيل أنك تكتب دالة تُرجع كائنًا ما يتوافق مع البروتوكول View. في SwiftUI، يحدث هذا باستمرار:
func makeGreeting() -> View {\n return Text("Hello!")\n}\n\nسيخبرك مترجم Swift: «لا يمكن استخدام البروتوكول 'View' إلا كقيد عام (generic constraint)، لأنه يحتوي على متطلبات Self أو associated type». يبدو الأمر مخيفًا، لكن الجوهر بسيط: البروتوكول View في SwiftUI يتطلب أن يكون النوع معروفًا في مرحلة الترجمة. إذا أعدنا View — فهذا تجريد، والمترجم يحتاج إلى التفاصيل.
في السابق، كان المطورون يتجاوزون ذلك باستخدام الأدوية العامة:
\n\nfunc makeGreeting<T: View>() -> T {\n return Text("Hello!") as! T\n}\n\nكان هذا يعمل، لكنه بدا كحل مؤقت: أنت تعد المترجم بأنك ستعيد T، لكنك في الواقع «تتظاهر». بالإضافة إلى ذلك، كان على الكود المستدعي تحديد النوع صراحةً. الأمر قبيح وغير آمن.
«Some» — المنقذ من الكود النمطي
\n\nظهرت الكلمة المفتاحية some في Swift في الإصدار 5.1 خصيصًا لمثل هذه الحالات. إنها تخبر المترجم: «سأعيد نوعًا محددًا يتوافق مع البروتوكول، لكنني لا أريد ذكر اسمه. أنت تحدده بنفسك في مرحلة الترجمة». وهذا ما يسمى النوع غير الشفاف (opaque type).
لنعد إلى مثالنا:
\n\nfunc makeGreeting() -> some View {\n return Text("Hello!")\n}\n\nالآن يتم تجميع الكود! يعرف المترجم أنه يتم إرجاع Text تحديدًا، لكن من الخارج نرى فقط some View. وهذا يعطي شيئين:
- \n
- سلامة الأنواع — يتحقق المترجم من أن النوع المُعاد هو نفسه دائمًا. \n
- التغليف — نخفي تفاصيل التنفيذ عن الكود المستدعي. \n
مقارنة: «some» مقابل الأدوية العامة مقابل البروتوكولات
\n\nلفهم سحر some بشكل أفضل، دعنا نقارن بين الطرق الثلاث على مثال محدد. لنفترض أننا نريد كتابة دالة تُرجع شكلًا (دائرة أو مربع).
1. إرجاع بروتوكول (لن يتم تجميعه)
\n\nprotocol Shape {\n func draw()\n}\n\nstruct Circle: Shape {\n func draw() { print("نرسم دائرة") }\n}\nstruct Square: Shape {\n func draw() { print("نرسم مربعًا") }\n}\n\n// خطأ: لا يمكن استخدام البروتوكول 'Shape' كنوع إرجاع\nfunc makeShape() -> Shape {\n return Circle()\n}\n\nكما نعلم بالفعل — لا يعمل إذا كان البروتوكول يحتوي على associated types أو Self requirements.
\n\n2. استخدام الأدوية العامة
\n\nfunc makeShape<T: Shape>() -> T {\n // نحتاج إلى إنشاء T بطريقة ما، لكننا لا نعرف النوع المحدد\n fatalError("لا يمكن إنشاء generic")\n}\n\nالمشكلة: يجب أن يعرف الكود المستدعي النوع المتوقع. لا تستطيع الدالة أن تقرر بنفسها ما ستعيده.
\n\n3. استخدام «some» (يعمل بشكل مثالي)
\n\nfunc makeShape() -> some Shape {\n