Reflection (التفكير البرمجي) هو آلية وقت التشغيل التي تسمح للكود باستكشاف بنيته الخاصة: الحصول على الفئات والطرق والحقول والتعليقات التوضيحية دون معرفة الأنواع في وقت الترجمة. هذه الأداة هي أساس العديد من أطر عمل الجوال — تسلسل JSON (Gson, Moshi)، حقن التبعيات (Dagger, Koin) ومشغّلات الاختبارات (JUnit, XCTest). وفقًا لـ دليل Reflection لـ Oracle Java، 2024، فإن reflection عنصر إلزامي في منصة Java تستخدمه جميع المكتبات الكبرى.
النقاط الرئيسية
Reflection هو قدرة البرنامج على مراقبة وتعديل بنيته وسلوكه أثناء التنفيذ. في اللغات الموجهة للكائنات، يعني ذلك الحصول على كائنات Class وMethod وField وConstructor، التي تمثل عناصر البرنامج كبيانات متاحة للقراءة والاستدعاء.
مصطلح «reflection» أُدخل في مجتمع الذكاء الاصطناعي عام 1982 (Brian Cantwell Smith) وتم تطبيقه في لغة Smalltalk. في تطوير الجوال، ظهر reflection لأول مرة في Java ME وObjective-C (1986, NextStep). اليوم، تمتلك كل منصة جوال كبرى واجهة reflection خاصة بها: Java/Kotlin لنظام Android، وObjective-C Runtime لنظام iOS، وSwift Mirror API لـ Swift.
آلية reflection تعتمد على البيانات الوصفية التي يحفظها المترجم في البايت كود أو الملف الثنائي. يخزن Android معلومات كاملة عن الفئات في ملفات DEX، بينما يخزنها iOS في قسم __objc_classlist من مقطع Mach-O. يقوم وقت التشغيل بتحميل هذه البيانات الوصفية إلى الذاكرة ويوفر واجهة برمجية لاجتيازها.
Java Reflection API يبنى حول الفئة java.lang.Class. يمكن تحويل أي كائن في Java إلى Class عبر .getClass() أو Class.forName(). ومن Class تُستخرج جميع الطرق والحقول والمنشئات والتعليقات التوضيحية والفئات الفائقة. يرث Kotlin تفكير Java ويضيف KClass وKFunction وKProperty الخاصة به من الحزمة kotlin.reflect.
import kotlin.reflect.full.declaredMemberFunctions
data class User(
val name: String,
val email: String
)
fun inspectClass() {
val kClass = User::class
val properties = kClass.declaredMemberProperties
val functions = kClass.declaredMemberFunctions
properties.forEach { prop ->
println("الخاصية: ${prop.name}، النوع: ${prop.returnType}")
}
}
في هذا المثال، KClass يوفر البيانات الوصفية لفئة البيانات User. يُرجع declaredMemberProperties قائمة بالخصائص مع أنواعها ومسترجعاتها. تفكير Kotlin متكامل بشكل وثيق مع coroutines: يدعم KFunction معدِّل suspend، مما يسمح باستدعاء الطرق غير المتزامنة عبر reflection.
تفكير Java يعمل مع Class<?> وMethod.setAccessible() وField.get(). يعمل setAccessible(true) على تعطيل فحوصات التحكم في الوصول للغة Java للعناصر الخاصة. هذه آلية قوية لكنها خطيرة: على Android بدءًا من API 28، قد يؤدي استدعاء setAccessible على طرق نظام مخفية إلى InaccessibleObjectException.
// Java reflection: استدعاء طريقة خاصة
Class> clazz = Class.forName("com.example.MyClass");
Object instance = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("privateMethod", String.class);
method.setAccessible(true);
method.invoke(instance, "reflection test");
يوضح الكود Class.forName() — التحميل الديناميكي للفئة حسب الاسم النصي. هذا هو أساس بنى الإضافات: قد تكون الفئة غير معروفة في وقت الترجمة، لكن يمكن تحميلها وتنفيذها عبر reflection في وقت التشغيل. يجد getDeclaredMethod(“privateMethod”, ...) الطريقة حسب الاسم وأنواع المعاملات، وينفذها invoke.
وقت تشغيل Objective-C يوفر الدوال class_copyMethodList وclass_copyPropertyList وobjc_getAssociatedObject. بخلاف Java، لا يخفي Objective-C الطرق الخاصة افتراضيًا — يرى وقت التشغيل جميع طرق الفئة. وهذا يفسر لماذا يعمل method swizzling بدون setAccessible: وقت التشغيل لا يملك تغليفًا على مستوى البيانات الوصفية.
Reflection يستخدم في المكتبات الرئيسية لتطوير الجوال. تسلسل JSON (Gson, Moshi, Kotlinx.serialization) يحصل على خصائص الكائن عبر reflection ويطابقها مع مفاتيح JSON. حقن التبعيات (Dagger, Koin, Swinject) يحلل المنشئات والحقول للحقن التلقائي للتبعيات. مكتبات ORM (Room, Realm) تستخدم reflection لربط الفئات بجداول قاعدة البيانات.
كل من هذه التطبيقات يعمل تحديدًا في وقت التشغيل — لا يعرف الكود مسبقًا الفئات التي سيواجهها. يوفر Reflection آلية عالمية لتجاوز هذا الغموض على حساب الأداء والأمان.
Reflection أبطأ من الاستدعاء المباشر للطرق بمقدار 10–100 مرة. السبب هو غياب تحسينات JIT (devirtualization, inlining)، والتحقق من الأنواع في كل استدعاء، وتغليف المعاملات في Object[]/varargs. لا يمكن لـ ART على Android 14 تحسين استدعاءات reflection عبر inline لأن الطريقة المستهدفة غير معروفة حتى لحظة التنفيذ.
| العملية | استدعاء مباشر | عبر Reflection | التباطؤ |
|---|---|---|---|
| استدعاء طريقة بدون معاملات | ~3 نانوثانية | ~120 نانوثانية | 40x |
| قراءة حقل int | ~1 نانوثانية | ~85 نانوثانية | 85x |
| استدعاء طريقة بمعاملين | ~4 نانوثانية | ~250 نانوثانية | 62x |
| إنشاء كائن عبر المنشئ | ~5 نانوثانية | ~180 نانوثانية | 36x |
| تحديد فئة حسب سلسلة نصية | — | ~800 نانوثانية | — |
تم الحصول على البيانات على Google Pixel 8 (Android 14, ART). يتحسن أداء reflection مع كل إصدار من Android: على Android 9 كان الاستدعاء عبر Method.invoke() أبطأ 150 مرة من المباشر، وعلى Android 14 أصبح أبطأ 40 مرة. يستخدم ART آليات method handle المدمجة للتحسين.
في المقاطع الحساسة للأداء، يستبدل المطورون reflection بتوليد الكود: يستخدم Dagger معالجة التعليقات التوضيحية بدلًا من البحث في وقت التشغيل، يولّد Kotlinx.serialization المُسلسِلات عبر KSP، ويطوّع Moshi @JsonClass(generateAdapter = true) لتوليد الكود في وقت الترجمة.
معالجة التعليقات التوضيحية (KAPT, KSP) وتوليد الكود هما البديلان الرئيسيان لـ reflection في تطوير الجوال. ينقلان تحليل البيانات الوصفية من وقت التشغيل إلى وقت الترجمة: يُولَّد الكود قبل بدء التطبيق، مما يلغي عبء reflection ويحسّن الأداء.
// KSP: توليد كود بدلًا من reflection
@Serializable
data class Config(
val apiUrl: String,
val timeout: Int
)
// KSP يولّد ConfigSerializer بدون reflection
fun loadConfig(json: String): Config {
return Config.serializer().decodeFromString(json)
}
في هذا المثال، @Serializable هو تعليق توضيحي لـ Kotlinx.serialization. يحلل KSP (Kotlin Symbol Processing) الكود المصدري في وقت الترجمة، ويجد جميع الفئات @Serializable ويولّد المُسلسِلات. أثناء تنفيذ التطبيق لا يُستخدم reflection — فالمُسلسِل مُجمَّع بالفعل في كود آلة.
توليد الكود يوفر أداءً أفضل وأمانًا للأنواع وحجم ملف ثنائي أصغر (إزالة الكود الميت تزيل اعتمادات reflection غير المستخدمة). يبقى reflection ضروريًا للمهام التي تكون فيها الأنواع غير معروفة في وقت الترجمة: التحميل الديناميكي للإضافات، والوكلاء في وقت التشغيل، وأدوات اختبار الشفرة. وفقًا لـ Kotlin، فإن Kotlinx.serialization مع KSP أسرع بمقدار 3–5 مرات من Gson المستند إلى reflection.
Reflection على منصات الجوال له قيود أمنية وأداء. يقيّد Android بدءًا من API 28 (Pie) setAccessible لواجهات non-SDK — محاولة فتح طريقة نظام مخفية تؤدي إلى استثناء أو تحذير. لا يدعم iOS مع Swift التفكير البرمجي بالمعنى التقليدي: يوفر Swift Mirror API قراءة الخصائص فقط (الاسم، القيمة) دون تعديل أو استدعاء طرق.
Google Play يرفض التطبيقات التي تستخدم reflection لتجاوز قيود المنصة: استبدال خدمات النظام، تعديل سياسات SELinux، قراءة أذونات محمية. كما تحظر Apple التطبيقات التي تستدعي واجهات برمجة خاصة عبر reflection — يفحص App Review الملف الثنائي بحثًا عن توقيعات objc_msgSend ذات المحددات الخاصة المعروفة.
ProGuard/R8 قيد آخر. تعمل التعتيم وتصغير الكود على إعادة تسمية الفئات والطرق إلى أسماء قصيرة (a, b, c). إذا كان الكود يستخدم Class.forName(“com.example.MyClass”)، فسوف ينكسر بعد التعتيم. الحل هو keep rules في proguard-rules.pro:
// قواعد keep الخاصة بـ ProGuard لـ reflection
-keep class com.example.** { *; }
-keep class * implements java.io.Serializable { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName ;
}
-keepattributes Signature, InnerClasses, EnclosingMethod
تخبر قواعد -keep أداة R8 بعدم إعادة تسمية الفئات المستخدمة عبر reflection. بدون هذه القواعد، سيتعطل التطبيق المعتم مع ClassNotFoundException — لن يتمكن وقت التشغيل من العثور على الفئة بالاسم النصي الذي تغيّر.
الأسئلة الشائعة
نعم، reflection أبطأ بمقدار 10–100 مرة من الاستدعاء المباشر. الأسباب الرئيسية: غياب تحسينات JIT (inlining, devirtualization)، وتغليف المعاملات، والتحقق من الأنواع في كل استدعاء. في الكود الإنتاجي يُنصح باستبدال reflection بتوليد الكود عبر KSP أو معالجة التعليقات التوضيحية.
تفكير Java يعمل عبر Class وMethod وField ويتطلب setAccessible للأعضاء الخاصين. يستخدم تفكير Kotlin KClass وKFunction وKProperty ويدعم sealed class وdata class وcoroutines (دوال suspend) وnull-safety. يعتمد تفكير Kotlin على تفكير Java لكنه يضيف واجهة آمنة للأنواع.
أضف keep rules الخاصة بـ ProGuard/R8 للفئات والطرق والحقول المستخدمة عبر reflection. لكل Class.forName() وgetDeclaredMethod() وgetDeclaredField() يجب أن توجد توجيهات -keep مطابقة. أدوات مثل GreenDAO وRoom تولّد keep rules تلقائيًا.
لا يملك Swift التفكير البرمجي بالمعنى الكامل. Mirror API (Swift 2+) يسمح بقراءة خصائص البنية أو الفئة: الاسم، القيمة، النوع. استدعاء الطرق وتعديل الحقول وإنشاء كائنات حسب النوع غير ممكنة. لهذا الغرض يُستخدم Objective-C Runtime عند الوراثة من NSObject مع @objc dynamic.
Gson (تسلسل JSON)، Retrofit (إنشاء تنفيذات الواجهات عبر dynamic proxy)، Mockito (إنشاء الكائنات المزيفة)، Koin (حقن التبعيات)، Room (التحقق من Entity في وقت الترجمة عبر KAPT)، Firebase Crashlytics (تحليل تتبع المكدس). معظم المكتبات تنتقل إلى توليد الكود مع KSP/KAPT.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.