Reflection (انعکاس) — مکانیزم زمان اجرا است که به کد امکان میدهد ساختار خود را بررسی کند: دریافت کلاسها، متدها، فیلدها و annotation ها بدون دانستن انواع در مرحله کامپایل. این ابزار در پایه بسیاری از فریمورکهای موبایل قرار دارد — سریالایز JSON (Gson, Moshi)، تزریق وابستگی (Dagger, Koin) و اجراکنندههای تست (JUnit, XCTest). بر اساس آموزش رسمی Reflection جاوا، Oracle، 2024، reflection عنصر اجباری پلتفرم Java است که توسط همه کتابخانههای بزرگ استفاده میشود.
نکات اصلی
Reflection — توانایی برنامه برای مشاهده و تغییر ساختار و رفتار خود در زمان اجرا. در زبانهای شیءگرا این به معنای دریافت اشیاء Class، Method، Field و Constructor است که عناصر برنامه را بهصورت دادههای قابل خواندن و فراخوانی نمایش میدهند.
اصطلاح «reflection» در سال ۱۹۸۲ در جامعه هوش مصنوعی (برایان کانتول اسمیت) معرفی و در زبان Smalltalk پیادهسازی شد. در توسعه موبایل، reflection اولین بار در Java ME و Objective-C (۱۹۸۶، NextStep) ظاهر شد. امروزه هر پلتفرم موبایل بزرگ API reflection خود را دارد: Java/Kotlin برای Android، Objective-C Runtime برای iOS، Swift Mirror API برای Swift.
مکانیزم reflection بر فرادادههایی استوار است که کامپایلر در بایتکد یا فایل باینری ذخیره میکند. Android اطلاعات کامل درباره کلاسها را در فایلهای DEX ذخیره میکند، iOS — در بخش __objc_classlist در Mach-O. Runtime این فرادادهها را در حافظه بارگذاری میکند و API برای پیمایش آنها فراهم میکند.
Java Reflection API حول کلاس java.lang.Class ساخته میشود. هر شیء در Java را میتوان از طریق .getClass() یا Class.forName() به Class تبدیل کرد. از Class همه متدها، فیلدها، سازندهها، annotation ها و کلاسهای والد استخراج میشوند. Kotlin reflection جاوا را به ارث میبرد و 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 فرادادههای data class User را فراهم میکند. declaredMemberProperties فهرست ویژگیها را همراه با نوعها و getter های آنها برمیگرداند. reflection در Kotlin بهطور نزدیک با coroutine ها ادغام شده است: KFunction از اصلاحکننده suspend پشتیبانی میکند که امکان فراخوانی متدهای ناهمگام را از طریق reflection فراهم میکند.
reflection در Java با Class<?>، Method.setAccessible() و Field.get() کار میکند. setAccessible(true) بررسی دسترسی Java language access control را برای عناصر private غیرفعال میکند. این مکانیزم قدرتمند اما خطرناک است: در Android از API 28 فراخوانی setAccessible روی متدهای مخفی سیستم میتواند InaccessibleObjectException ایجاد کند.
// 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 runtime توابع class_copyMethodList، class_copyPropertyList، objc_getAssociatedObject را فراهم میکند. برخلاف Java، Objective-C متدهای خصوصی را بهطور پیشفرض پنهان نمیکند — runtime همه متدهای کلاس را میبیند. این توضیح میدهد چرا method swizzling بدون setAccessible کار میکند: runtime در سطح فرادادهها کپسولهسازی ندارد.
Reflection در کتابخانههای کلیدی توسعه موبایل استفاده میشود. سریالایز JSON (Gson, Moshi, Kotlinx.serialization) ویژگیهای شیء را از طریق reflection دریافت میکند و آنها را با کلیدهای JSON مطابقت میدهد. تزریق وابستگی (Dagger, Koin, Swinject) سازندهها و فیلدها را برای تزریق خودکار وابستگیها تحلیل میکند. کتابخانههای ORM (Room, Realm) از reflection برای نگاشت کلاسها به جدولهای پایگاه داده استفاده میکنند.
هر یک از این کاربردها دقیقاً در زمان اجرا کار میکند — کد از قبل نمیداند با چه کلاسهایی مواجه خواهد شد. Reflection مکانیزمی جهانی برای عبور از این ناشناختگی به بهای عملکرد و امنیت فراهم میکند.
Reflection ۱۰–۱۰۰ برابر کندتر از فراخوانی مستقیم متدها است. دلیل — نبود بهینهسازیهای JIT (devirtualization، inlining)، بررسی انواع در هر فراخوانی و بستهبندی پارامترها در Object[]/varargs. ART در Android 14 نمیتواند فراخوانیهای reflection را بهصورت inline بهینه کند، زیرا متد هدف تا لحظه اجرا ناشناخته است.
| عملیات | فراخوانی مستقیم | از طریق Reflection | کاهش سرعت |
|---|---|---|---|
| فراخوانی متد بدون پارامتر | ~3 ns | ~120 ns | 40x |
| خواندن فیلد int | ~1 ns | ~85 ns | 85x |
| فراخوانی متد با 2 پارامتر | ~4 ns | ~250 ns | 62x |
| ایجاد نمونه از طریق سازنده | ~5 ns | ~180 ns | 36x |
| تعیین کلاس بر اساس رشته | — | ~800 ns | — |
دادهها بر روی Google Pixel 8 (Android 14، ART) به دست آمدهاند. عملکرد reflection با هر نسخه Android بهبود مییابد: در Android 9 فراخوانی از طریق Method.invoke() ۱۵۰ برابر کندتر از مستقیم بود، در Android 14 — ۴۰ برابر. ART از مکانیزمهای داخلی method handle برای بهینهسازی استفاده میکند.
برای بخشهای حساس به عملکرد، توسعهدهندگان reflection را با code generation جایگزین میکنند: Dagger بهجای جستجوی زمان اجرا از annotation processing استفاده میکند، Kotlinx.serialization سریالایزرها را از طریق KSP تولید میکند، Moshi برای codegen در مرحله کامپایل @JsonClass(generateAdapter = true) را تطبیق میدهد.
Annotation processing (KAPT، KSP) و code generation — جایگزینهای اصلی reflection در توسعه موبایل هستند. آنها تحلیل فرادادهها را از زمان اجرا به مرحله کامپایل منتقل میکنند: کد قبل از راهاندازی برنامه تولید میشود که سربار reflection را حذف و عملکرد را بهبود میدهد.
// KSP: code generation به جای 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 annotation سریالایز کاتلین است. KSP (Kotlin Symbol Processing) کد منبع را در مرحله کامپایل تحلیل میکند، همه کلاسهای @Serializable را پیدا و سریالایزرها را تولید میکند. در زمان اجرای برنامه reflection استفاده نمیشود — سریالایزر قبلاً به کد ماشین کامپایل شده است.
Code generation عملکرد بهتر، امنیت نوع و اندازه باینری کوچکتر فراهم میکند (dead code elimination وابستگیهای بلااستفاده reflection را حذف میکند). Reflection برای کارهایی که انواع در مرحله کامپایل ناشناختهاند ضروری باقی میماند: بارگذاری پویای پلاگینها، پروکسی زمان اجرا، ابزاربندی تستها. بر اساس دادههای Kotlin، Kotlinx.serialization با KSP ۳–۵ برابر سریعتر از Gson مبتنی بر reflection است.
Reflection در پلتفرمهای موبایل محدودیتهای امنیتی و عملکردی دارد. Android از API 28 (Pie) setAccessible را برای رابطهای غیر SDK محدود میکند — تلاش برای باز کردن متد مخفی سیستم به استثنا یا هشدار منجر میشود. iOS با Swift از reflection به معنای کلاسیک پشتیبانی نمیکند: Swift Mirror API فقط خواندن ویژگیها (name, value) را بدون تغییر یا فراخوانی متدها فراهم میکند.
Google Play برنامههایی را که از reflection برای دور زدن محدودیتهای پلتفرم استفاده میکنند رد میکند: جایگزینی سرویسهای سیستم، تغییر سیاستهای SELinux، خواندن مجوزهای محافظتشده. Apple نیز برنامههایی را که از طریق reflection API خصوصی فراخوانی میکنند مسدود میکند — بررسی App Review فایل باینری را برای امضاهای رشتهای objc_msgSend با سلکتورهای خصوصی شناختهشده اسکن میکند.
ProGuard/R8 — محدودیت دیگری است. مبهمسازی و فشردهسازی کد، کلاسها و متدها را به نامهای کوتاه (a, b, c) تغییر نام میدهد. اگر کد از Class.forName(«com.example.MyClass») استفاده کند، پس از مبهمسازی خراب میشود. راهحل — قوانین keep در proguard-rules.pro:
// قوانین keep برای reflection در ProGuard
-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 سقوط میکند — runtime نمیتواند کلاس را با نام رشتهای تغییر یافته پیدا کند.
سوالات متداول
بله، reflection ۱۰–۱۰۰ برابر کندتر از فراخوانی مستقیم است. دلایل اصلی: نبود بهینهسازیهای JIT (inlining، devirtualization)، بستهبندی پارامترها و بررسی انواع در هر فراخوانی. برای کد production توصیه میشود reflection را با code generation از طریق KSP یا annotation processing جایگزین کنید.
reflection جاوا از طریق Class، Method، Field کار میکند و برای اعضای private به setAccessible نیاز دارد. reflection کاتلین از KClass، KFunction، KProperty استفاده میکند و از sealed class، data class، coroutine ها (توابع suspend) و null-safety پشتیبانی میکند. reflection کاتلین بر reflection جاوا استوار است اما API type-safe اضافه میکند.
برای کلاسها، متدها و فیلدهای استفادهشده از طریق reflection، قوانین keep مربوط به ProGuard/R8 را اضافه کنید. برای هر Class.forName()، getDeclaredMethod()، getDeclaredField() باید دستورالعمل -keep مربوطه وجود داشته باشد. ابزارهایی مانند GreenDAO و Room بهطور خودکار قوانین keep تولید میکنند.
Swift به معنای کامل reflection ندارد. Mirror API (Swift 2+) امکان خواندن ویژگیهای ساختار یا کلاس را فراهم میکند: نام، مقدار، نوع. فراخوانی متدها، تغییر فیلدها و ایجاد نمونه بر اساس نوع ممکن نیست. برای این کار هنگام ارثبری از NSObject با @objc dynamic از Objective-C Runtime استفاده میشود.
Gson (سریالایز JSON)، Retrofit (ایجاد implementation رابطها از طریق dynamic proxy)، Mockito (ایجاد mock)، Koin (تزریق وابستگی)، Room (بررسی Entity در مرحله کامپایل از طریق KAPT)، Firebase Crashlytics (تحلیل stack trace). بیشتر کتابخانهها به code generation با KSP/KAPT روی میآورند.
جمعبندی
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید