إصلاح مشاكل الثبات

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

تفعيل ميزة التخطي السريع

عليك أولاً محاولة تفعيل وضع التخطّي القوي. يتيح وضع التخطّي القوي تخطّي العناصر القابلة للإنشاء التي تتضمّن مَعلمات غير ثابتة، وهو أسهل طريقة لحلّ مشاكل الأداء الناتجة عن عدم الثبات.

يمكنك الاطّلاع على التخطّي القوي لمزيد من المعلومات.

جعل الفئة غير قابلة للتغيير

يمكنك أيضًا محاولة جعل فئة غير ثابتة غير قابلة للتغيير تمامًا.

  • غير قابل للتغيير: يشير إلى نوع لا يمكن فيه تغيير قيمة أي من السمات بعد إنشاء مثيل من هذا النوع، وتكون جميع الطرق شفافة من الناحية المرجعية.
    • تأكَّد من أنّ جميع خصائص الفئة هي val وليست var، ومن أنّها من الأنواع غير القابلة للتغيير.
    • تكون الأنواع الأساسية، مثل String, Int وFloat، غير قابلة للتغيير دائمًا.
    • إذا كان ذلك غير ممكن، عليك استخدام حالة Compose لأي خصائص قابلة للتغيير.
  • ثابت: يشير إلى نوع قابل للتغيير. لا يمكن لوقت تشغيل Compose معرفة ما إذا كان أي من الخصائص العامة أو سلوك الطريقة الخاصة بالنوع سيؤدي إلى نتائج مختلفة عن استدعاء سابق، أو معرفة وقت حدوث ذلك.

المجموعات غير القابلة للتغيير

من الأسباب الشائعة التي تجعل Compose تعتبر إحدى الفئات غير مستقرة هي المجموعات. كما هو موضّح في صفحة تشخيص مشاكل الثبات، لا يمكن لمترجم Compose التأكّد تمامًا من أنّ المجموعات، مثل List, Map وSet، غير قابلة للتغيير، وبالتالي يضع علامة عليها باعتبارها غير ثابتة.

لحلّ هذه المشكلة، يمكنك استخدام مجموعات غير قابلة للتغيير. يتوافق برنامج التجميع في Compose مع مجموعات Kotlinx غير القابلة للتغيير. ويُضمَن أنّ هذه المجموعات غير قابلة للتغيير، ويتعامل معها برنامج التجميع البرمجي في Compose على هذا الأساس. لا تزال هذه المكتبة في مرحلة الإصدار الأوّلي، لذا من المتوقّع إجراء تغييرات محتملة على واجهة برمجة التطبيقات.

راجِع مرة أخرى هذه الفئة غير الثابتة من دليل تشخيص مشاكل الثبات:

unstable class Snack {
  
  unstable val tags: Set<String>
  
}

يمكنك جعل tags ثابتًا باستخدام مجموعة غير قابلة للتغيير. في الصف، غيِّر نوع tags إلى ImmutableSet<String>:

data class Snack{
    
    val tags: ImmutableSet<String> = persistentSetOf()
    
}

بعد ذلك، تصبح جميع مَعلمات الفئة غير قابلة للتغيير، ويضع برنامج التجميع Compose علامة "ثابتة" على الفئة.

إضافة تعليقات توضيحية باستخدام Stable أو Immutable

من الطرق المحتملة لحلّ مشاكل الثبات إضافة التعليق التوضيحي @Stable أو @Immutable إلى الفئات غير الثابتة.

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

إذا كان من الممكن جعل صفك ثابتًا بدون إضافة تعليق توضيحي، عليك السعي لتحقيق الثبات بهذه الطريقة.

يقدّم المقتطف التالي مثالاً بسيطًا على فئة بيانات تم وضع تعليق توضيحي عليها بأنّها غير قابلة للتغيير:

@Immutable
data class Snack(

)

سواء كنت تستخدم التعليق التوضيحي @Immutable أو @Stable، سيضع برنامج التجميع Compose علامة على الفئة Snack باعتبارها فئة ثابتة.

الفئات التي تتضمّن تعليقات توضيحية في المجموعات

لنفترض أنّ لديك دالة قابلة للإنشاء تتضمّن مَعلمة من النوع List<Snack>:

restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  
  unstable snacks: List<Snack>
  
)

حتى إذا أضفت التعليق التوضيحي @Immutable إلى Snack، سيظل برنامج التجميع Compose يضع علامة على المَعلمة snacks في HighlightedSnacks باعتبارها غير ثابتة.

تواجه المَعلمات المشكلة نفسها التي تواجهها الفئات عندما يتعلّق الأمر بأنواع المجموعات، إذ يضع محرِّر Compose دائمًا علامة "غير مستقر" على مَعلمة من النوع List، حتى إذا كانت مجموعة من الأنواع المستقرة.

لا يمكنك وضع علامة "ثابت" على مَعلمة فردية، ولا يمكنك إضافة تعليق توضيحي إلى عنصر قابل للإنشاء ليكون قابلاً للتخطّي دائمًا. هناك مسارات متعدّدة للمضي قدمًا.

هناك عدة طرق يمكنك من خلالها حلّ مشكلة المجموعات غير المستقرة. توضّح الأقسام الفرعية التالية هذه الأساليب المختلفة.

ملف الإعداد

إذا كنت على استعداد للالتزام بعقد الثبات في قاعدة الرموز البرمجية، يمكنك الموافقة على اعتبار مجموعات Kotlin ثابتة من خلال إضافة kotlin.collections.* إلى ملف إعدادات الثبات.

مجموعة غير قابلة للتغيير

لضمان عدم التغيير في وقت الترجمة، يمكنك استخدام مجموعة kotlinx غير قابلة للتغيير بدلاً من List.

@Composable
private fun HighlightedSnacks(
    
    snacks: ImmutableList<Snack>,
    
)

Wrapper

إذا لم تتمكّن من استخدام مجموعة غير قابلة للتغيير، يمكنك إنشاء مجموعة خاصة بك. لإجراء ذلك، ضمِّن List في فئة ثابتة مزوّدة بتعليقات توضيحية. من المرجّح أنّ يكون برنامج تضمين عام هو الخيار الأفضل لذلك، وذلك حسب متطلباتك.

@Immutable
data class SnackCollection(
   val snacks: List<Snack>
)

يمكنك بعد ذلك استخدام هذا النوع كنوع المَعلمة في العنصر القابل للإنشاء.

@Composable
private fun HighlightedSnacks(
    index: Int,
    snacks: SnackCollection,
    onSnackClick: (Long) -> Unit,
    modifier: Modifier = Modifier
)

الحل

بعد اتّباع أيّ من هذين الأسلوبَين، يضع الآن برنامج التجميع Compose علامة على الدالة البرمجية القابلة للإنشاء HighlightedSnacks باعتبارها skippable وrestartable.

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  stable index: Int
  stable snacks: ImmutableList<Snack>
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
)

أثناء إعادة التركيب، يمكن الآن لـ Compose تخطّي HighlightedSnacks إذا لم تتغيّر أي من مدخلاته.

ملف إعداد الاستقرار

بدءًا من الإصدار 1.5.5 من Compose Compiler، يمكن توفير ملف إعداد لفئات يجب اعتبارها مستقرة في وقت الترجمة. يتيح ذلك اعتبار الفئات التي لا تتحكّم فيها، مثل فئات المكتبة العادية مثل LocalDateTime، فئات ثابتة.

ملف الإعدادات هو ملف نص عادي يحتوي على فئة واحدة في كل صف. يمكن استخدام التعليقات وأحرف البدل المفردة والمزدوجة.

مثال على الضبط:

// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>

لتفعيل هذه الميزة، مرِّر مسار ملف الإعداد إلى كتلة الخيارات composeCompiler في إعداد مكوّن Gradle الإضافي لمترجم Compose.

composeCompiler {
  stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}

بما أنّ برنامج التجميع في Compose يعمل على كل وحدة في مشروعك بشكل منفصل، يمكنك تقديم إعدادات مختلفة لوحدات مختلفة إذا لزم الأمر. بدلاً من ذلك، يمكنك ضبط إعداد واحد على مستوى الجذر في مشروعك وتمرير هذا المسار إلى كل وحدة.

وحدات متعدّدة

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

إذا كانت طبقة البيانات في وحدة منفصلة عن طبقة واجهة المستخدم، وهو الأسلوب المقترَح، قد تواجه هذه المشكلة.

الحل

لحلّ هذه المشكلة، يمكنك اتّباع أحد الأساليب التالية:

  1. أضِف الفئات إلى ملف إعداد المترجم.
  2. فعِّل برنامج التجميع Compose في وحدات طبقة البيانات، أو ضع علامة @Stable أو @Immutable على الفئات حسب الاقتضاء.
    • يتضمّن ذلك إضافة تبعية Compose إلى طبقة البيانات. ومع ذلك، هذه هي التبعية لوقت تشغيل Compose فقط، وليس لـ Compose-UI.
  3. ضِمن وحدة واجهة المستخدِم، لفّ فئات طبقة البيانات في فئات التفاف خاصة بواجهة المستخدِم.

تحدث المشكلة نفسها أيضًا عند استخدام مكتبات خارجية إذا كانت لا تستخدم برنامج التجميع Compose.

ليس كل عنصر قابل للإنشاء يجب أن يكون قابلاً للتخطّي

عند العمل على حلّ المشاكل المتعلّقة بالثبات، يجب عدم محاولة جعل كل عنصر قابل للتخطّي. وقد يؤدي محاولة إجراء ذلك إلى تحسين مبكر يؤدي إلى حدوث مشاكل أكثر من المشاكل التي يحلّها.

هناك العديد من الحالات التي لا يكون فيها إمكانية التخطّي مفيدة وقد تؤدي إلى صعوبة صيانة الرمز البرمجي. مثلاً:

  • دالة قابلة للإنشاء لا تتم إعادة تركيبها غالبًا أو لا تتم إعادة تركيبها على الإطلاق
  • عنصر قابل للإنشاء يستدعي في حد ذاته عناصر قابلة للإنشاء يمكن تخطّيها.
  • عنصر قابل للإنشاء يتضمّن عددًا كبيرًا من المَعلمات التي تتضمّن عمليات تنفيذ مكلفة للدالة equals. في هذه الحالة، قد تفوق تكلفة التحقّق مما إذا كانت أي مَعلمة قد تغيّرت تكلفة إعادة التركيب الرخيصة.

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