دوال التضمين (Inline) في Kotlin: متى تكون ضرورية حقًا ومتى تضر

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

دوال التضمين (Inline) في Kotlin: سحر التحسين أم فخ للمبتدئين؟



إذا كنت قد كتبت يومًا بلغة Kotlin وألقيت نظرة على المكتبة القياسية، فمن المؤكد أنك رأيت الكلمة الغامضة inline قبل تعريف الدوال. ربما أضفتها أيضًا إلى كودك، متبعًا مثال زملائك "ذوي الخبرة". لكن هل تفهم حقًا متى تفيد دالة التضمين (Inline) ومتى تؤدي فقط إلى تضخيم البايت كود وإبطاء التجميع؟



هذا السؤال هو أحد أكثر الأسئلة شيوعًا على Stack Overflow (آلاف المشاهدات!)، وليس بدون سبب. لن ننسخ الأدلة الجافة. بدلاً من ذلك، سنحلل ثلاثة سيناريوهات حقيقية حيث تصبح دوال التضمين أفضل صديق لك، وسيناريو واحد حيث من الأفضل التخلي عنها. هيا بنا!



ما هي دالة التضمين (Inline) في 30 ثانية (للتذكير)



عندما تضع علامة inline على دالة، لا يقوم المترجم بإنشاء كائن منفصل لها في الذاكرة. بدلاً من ذلك، يقوم حرفيًا "بلصق" جسم الدالة في كل مكان يتم استدعاؤها فيه. تخيل أنك تنسخ قطعة من الكود وتلصقها مباشرة في نقطة الاستدعاء - هذا ما يحدث تحت الغطاء.



بدون inline:

fun measureTime(action: () -> Unit) {    val start = System.currentTimeMillis()    action()    println("Time: ${System.currentTimeMillis() - start}ms")}

fun main() { measureTime { /* الكود الخاص بك */ }}


مع inline:

inline fun measureTime(action: () -> Unit) {    val start = System.currentTimeMillis()    action()    println("Time: ${System.currentTimeMillis() - start}ms")}

fun main() { measureTime { /* الكود الخاص بك */ }}


الفرق غير مرئي للعين، لكن المترجم سينشئ بايت كود مختلفًا تمامًا. في الحالة الأولى، سيتم إنشاء كائن لامبدا مجهول، وفي الحالة الثانية، سيتم نسخ الكود ببساطة. الآن دعنا نفهم متى يكون هذا مهمًا حقًا.



السيناريو رقم 1: دوال الرتبة الأعلى (Higher-order functions) مع اللامبدات (الحالة الأكثر شيوعًا)



إذا كانت الدالة الخاصة بك تستقبل دالة أخرى كمعامل (ما يسمى دالة الرتبة الأعلى)، فإن كل استدعاء للامبدا ينشئ فئة مجهولة. في جزء ساخن من الكود يتم استدعاؤه آلاف المرات في الثانية، يمكن أن يؤدي هذا إلى:

  • تخصيص إضافي للكائنات في الكومة (Heap)
  • ضغط على جامع القمامة (Garbage Collector)
  • فقدان في الأداء يصل إلى 10-20%


متى ينقذك التضمين (Inline):

  • عند استدعاء نفس الدالة مع لامبدات بشكل متكرر في حلقة
  • عندما تكون الدالة جزءًا من مكتبة، ولا تعرف كيف سيتم استخدامها
  • عندما تلتقط اللامبدا العديد من المتغيرات (يقلل التضمين من النفقات الإضافية)


// سيء بدون inline (إذا تم استدعاؤها بشكل متكرر)fun repeatAction(times: Int, action: () -> Unit) {    for (i in 0 until times) {        action()    }}

// جيد مع inlineinline fun repeatAction(times: Int, action: () -> Unit) { for (i in 0 until times) { action() }}


لكن هناك فارق بسيط: إذا كانت الدالة الخاصة بك تُستدعى نادرًا (مرة في الدقيقة) أو كانت اللامبدا كبيرة، فإن الفائدة من التضمين تقترب من الصفر. لا تجعل الدالة "مضمنة (inline)" لمجرد الاحتياط.



السيناريو رقم 2: الأدوية المحققة (Reified generics) - الطريقة الوحيدة للعمل مع الأنواع في وقت التشغيل



في Java و Kotlin، يتم مسح الأدوية (Generics) أثناء التجميع. لا يمكنك كتابة if (value is T) أو T::class.java داخل دالة عادية. لكن مع التضمين (inline) و reified يصبح هذا ممكنًا!



مثال من الحياة الواقعية - إنشاء مثيل حسب النوع:



// هذا لن يتم تجميعهfun <T> createInstance(): T {    return T::class.java.newInstance() // خطأ!}

// وهذا يعمل!inline fun <reified T> createInstance(): T { return T::class.java.newInstance()}


بدون التضمين (inline)، لن تتمكن من استخدام reified. هذه هي الحالة الوحيدة التي يكون فيها التضمين (inline) إلزاميًا، وليس مجرد

المدونات

توصيات الكتب