لماذا يحتاج مطور Swift إلى اسم الكلاس كسلسلة نصية؟
\n\nتخيل الموقف: أنت تطور نظام تسجيل (logging) لتطبيق معقد. يجب أن يحتوي كل حدث ليس فقط على الرسالة، بل أيضًا على اسم الكلاس الذي وقع فيه الحدث. أو ربما تبني محلل JSON عام يحتاج إلى تحديد نوع النموذج (model) بناءً على مفتاح نصي. أو تقوم بتصحيح أخطاء retain cycle وتريد عرض جميع الكائنات في الذاكرة ديناميكيًا مع أسماء كلاساتها.
\n\nفي كل هذه الحالات، تحتاج إلى شيء واحد: الحصول على اسم كلاس الكائن في Swift كسلسلة نصية. قد يبدو الأمر بسيطًا، لكنه ليس بهذه الوضوح. في Swift، على عكس Objective-C، لا توجد طريقة "سحرية" واحدة تعمل بشكل مثالي في جميع المواقف. علاوة على ذلك، توفر الطرق المختلفة أداءً متفاوتًا، وبعضها يعطي نتائج غير متوقعة.
\n\nفي هذه المقالة، لن نقدم لك إجابة جاهزة فحسب، بل سنستعرض جميع الطرق للحصول على class name في Swift، ونقارن سرعتها، ونوضح متى يجب استخدام كل طريقة. ستتعلم كيفية تجنب المزالق وكتابة كود موثوق في بيئة الإنتاج (production).
\n\nالطريقة التقليدية: String(describing: type(of:))
\n\nالإجابة الأكثر شيوعًا على Stack Overflow حول هذا الموضوع هي استخدام مزيج من type(of:) و String(describing:). دعنا نشرح كيفية عملها.
class Car {\n var model: String\n init(model: String) {\n self.model = model\n }\n}\n\nlet myCar = Car(model: "Tesla Model 3")\nlet className = String(describing: type(of: myCar))\nprint(className) // "Car"\n\nهذه الطريقة بسيطة وبديهية. type(of:) تُرجع النوع الفوقي (metatype) للكائن، و String(describing:) تحوله إلى سلسلة نصية. لكن هناك فارق بسيط: هذه الطريقة لا تعمل بشكل صحيح مع الأنواع الاختيارية (optionals) والبروتوكولات (protocols).
مشكلة مع الاختياريات (Optionals)
\n\nlet optionalCar: Car? = Car(model: "BMW")\nlet optionalName = String(describing: type(of: optionalCar))\nprint(optionalName) // "Optional<Car>" — ليس ما توقعته!\n\nبدلاً من "Car"، تحصل على "Optional<Car>". يحدث هذا لأن type(of:) تُرجع نوع القيمة نفسها، وليس محتواها. بالنسبة للاختياري، هذا هو Optional<Car>.
مشكلة مع البروتوكولات والوراثة
\n\nprotocol Drivable { }\nextension Car: Drivable { }\n\nlet drivableObject: Drivable = Car(model: "Audi")\nlet protocolName = String(describing: type(of: drivableObject))\nprint(protocolName) // "Car" — هذه المرة نجح الأمر، لكن ليس دائمًا\n\nمع البروتوكولات، قد تكون النتيجة غير متوقعة، خاصةً عند استخدام تركيب البروتوكولات (protocol composition) أو Any. الخلاصة: هذه الطريقة جيدة للحالات البسيطة، لكنها ليست مناسبة لكود الإنتاج (production) الذي يتعامل مع الاختياريات والأنواع المعقدة.
إرث Objective-C: NSStringFromClass
\n\nإذا كنت قادمًا من Objective-C، فبالتأكيد تتذكر NSStringFromClass([self class]). في Swift، هذا يعمل أيضًا، ولكن مع بعض التحفظات:
import Foundation\n\nclass Motorcycle {\n var brand: String\n init(brand: String) {\n self.brand = brand\n }\n}\n\nlet bike = Motorcycle(brand: "Harley")\nlet objcClassName = NSStringFromClass(type(of: bike))\nprint(objcClassName) // "_TtC11YourModule11Motorcycle" — اسم مشوه (mangled)!\n\nفي Swift، الكلاسات لها أسماء "مشوهة" (mangled names). بدلاً من "Motorcycle"، تحصل على شيء مثل _TtC11YourModule11Motorcycle. للحصول على الاسم النقي، يجب "فك تشفيره" يدويًا أو استخدام String(describing:).
لكن هناك ميزة مهمة: NSStringFromClass تعمل مع أي كائن، بما في ذلك الاختياريات، ولا تتعطل عند nil. ولكن من أجل نقاء الكود، من الأفضل عدم استخدامها مباشرة.
متى نحتاج NSStringFromClass حقًا؟
\n- \n
- عند العمل مع