Lazy Loading هي استراتيجية للتحميل المؤجل للبيانات والصور والمكونات، حيث لا يتم طلب الموارد عند بدء تشغيل التطبيق، ولكن في اللحظة التي يحتاجها المستخدم بالفعل. وفقاً لـ دليل Android Paging 3، فإن التحميل البطيء للقوائم يقلل استهلاك الذاكرة بنسبة 60–80% عند العمل مع مجموعات بيانات كبيرة. التهيئة المؤجلة هي المبدأ الأساسي الذي تقوم عليه جميع تطبيقات Lazy Loading.
الخلاصة
Lazy Loading (التحميل البطيء، التحميل المؤجل) هو نمط تصميم وتحسين يتم فيه تحميل موارد التطبيق ليس عند بدء التشغيل، ولكن قبل الاستخدام مباشرة. في تطوير التطبيقات المحمولة، يُطبق Lazy Loading على ثلاث فئات رئيسية: البيانات (ترقيم القوائم)، الصور (التحميل عند التمرير)، والمكونات (الحاويات والعروض البطيئة).
عكس Lazy Loading هو Eager Loading (التحميل المبكر)، حيث يتم تحميل جميع الموارد عند بدء تشغيل الشاشة. Eager Loading أبسط في التطبيق ولكنه يستهلك ذاكرة أكثر ويزيد من وقت العرض الأول. للقوائم التي تحتوي على آلاف العناصر، يؤدي Eager Loading إلى OOM (نفاد الذاكرة) على الأجهزة ذات الذاكرة المحدودة. يحل Lazy Loading هذه المشكلة عن طريق تحميل ما هو مرئي على الشاشة فقط وتحميل الباقي أثناء التمرير.
في سياق iOS و Android، يتم تطبيق Lazy Loading على مستويات مختلفة. يوفر SwiftUI LazyVStack و LazyHStack للتقديم البطيء. يستخدم UIKit UITableView مع dequeueReusableCell. يستخدم Android RecyclerView مع مجموعة ViewHolder. على مستوى البيانات، Room مع Paging 3 و Core Data مع NSFetchedResultsController. يعتمد اختيار التقنية المحددة على الكومة التقنية ومتطلبات الأداء.
المبدأ المركزي لـ Lazy Loading هو تحميل الكمية المحددة من البيانات اللازمة للحالة الحالية للشاشة، بالإضافة إلى مخزن استباقي للتمرير السلس. يعتمد هذا النهج على آليتين: تتبع الرؤية وظاهرية العناصر.
آلية التتبع تحدد العناصر الموجودة في المنطقة المرئية من الشاشة (viewport). على Android، يقوم بذلك LinearLayoutManager أو GridLayoutManager من خلال طريقتي findFirstVisibleItemPosition و findLastVisibleItemPosition. في iOS، يوفر UIScrollView خصائص bounds.origin.y و contentOffset.height لحساب المنطقة المرئية. عندما يدخل عنصر إلى viewport (أو مخزن التحميل المسبق)، يبدأ تحميله. عندما يغادر عنصر الشاشة، يمكن تحرير موارده أو نقلها إلى ذاكرة التخزين المؤقت.
// Android — تتبع الرؤية في RecyclerView
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val lastVisible = layoutManager.findLastVisibleItemPosition()
val totalCount = layoutManager.itemCount
// تحميل الصفحة التالية إذا تبقى < 5 عناصر
if (lastVisible >= totalCount - 5) {
loadMoreItems()
}
}
})
مخزن التحميل المسبق هو تحميل استباقي للعناصر التي ستظهر قريباً على الشاشة. يدعم RecyclerView GapWorker.Prefetch عبر layoutManager.setItemPrefetchEnabled(true). يدعم iOS UITableView التحميل المسبق عبر UITableViewDataSourcePrefetching. عادةً ما يكون حجم مخزن التحميل المسبق من 1 إلى 2 شاشة للأمام، مما يوفر توازناً بين سلاسة التمرير واستهلاك الذاكرة. مخزن تحميل مسبق كبير جداً يلغي مزايا Lazy Loading، والصغير جداً يخلق فراغات فارغة أثناء التمرير السريع.
الصور هي أثقل نوع من الموارد في التطبيقات المحمولة. قد تستهلك صورة واحدة بدقة 12 ميجابكسل من 3 إلى 5 ميجابايت في شكل غير مضغوط. يمنع Lazy Loading للصور تحميل مئات الصور غير المرئية في الذاكرة، وهو ما سيكون كارثياً للقوائم التي تحتوي على صور المستخدمين الرمزية أو كتالوجات المنتجات.
Glide هي مكتبة تحميل الصور الأكثر شعبية لنظام Android مع دعم التخزين المؤقت والتحويلات والرسوم المتحركة. Coil هو بديل أخف، مكتوب بلغة Kotlin باستخدام coroutines. كلا المكتبتين توقفان التحميل تلقائياً عندما يغادر ImageView الشاشة وتلغي الطلبات عند إعادة استخدام ViewHolder. يستخدم Coil coroutines ويبلغ حجمه ~1.5 ميجابايت مقابل ~4 ميجابايت لـ Glide، مما يجعله مفضلاً للمشاريع التي تركز على حجم APK.
// Coil — تحميل الصور البطيء
imageView.load("https://example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
size(512, 512)
memoryCachePolicy(CachePolicy.ENABLED)
}
Kingfisher هي مكتبة لنظام iOS مع دعم Swift Concurrency والتخزين المؤقت على القرص وفي الذاكرة، والتحميل المسبق لـ UICollectionView. SDWebImage هي مكتبة أقدم بجذور Objective-C ولكن مع دعم Swift. تتكامل كلتا المكتبتين مع UIImageView وتديران دورة حياة التحميل تلقائياً: تلغي الطلبات عند إعادة استخدام الخلية، وتحمّل الصور فقط عندما تكون الخلية مرئية، وتحرر الذاكرة عند إشعار نقص الذاكرة.
القوائم الكبيرة من البيانات هي المجال الرئيسي لتطبيق Lazy Loading في التطبيقات المحمولة. خلاصات الأخبار، كتالوجات المنتجات، الدردشات، سجل العمليات — أي شاشة تحتوي على قائمة غير محدودة محتملة تتطلب ترقيم الصفحات والتحميل المؤجل.
Paging 3 هي مكتبة من Android Jetpack تنفذ دورة التحميل البطيء الكاملة: طلب البيانات من RemoteMediator (API + قاعدة البيانات)، التخزين المؤقت في Room، الإخراج صفحة بصفحة عبر PagingData، والعرض عبر AsyncPagingDataAdapter. يدعم Paging 3 ثلاثة أنواع من الترقيم: قائم على الصفحات، قائم على العناصر (offset/limit)، وقائم على المفاتيح (مفاتيح الترقيم من API). Separator هو دعم مدمج للفواصل بين الصفحات لمؤشرات التحميل.
// Paging 3 — تحميل بطيء من API
class ArticlePagingSource(
private val api: ArticleApi
) : PagingSource<Int, Article>() {
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, Article> {
return try {
val page = params.key ?: 1
val response = api.getArticles(page)
LoadResult.Page(
data = response.items,
prevKey = page.takeIf { it > 1 }?.dec(),
nextKey = page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
LazyVStack حاوية مدمجة في SwiftUI تنشئ وتعرض العناصر فقط عند ظهورها على الشاشة. على عكس VStack، الذي يحسب تخطيط جميع العناصر الفرعية فوراً، يؤجل LazyVStack إنشاء العرض حتى يصبح العنصر مرئياً أو يدخل في نطاق التحميل المسبق. LazyHStack هو النظير الأفقي للدوّارات. للقوائم الكبيرة، توصي Apple باستخدام List، التي تعمل داخلياً بشكل مشابه لـ LazyVStack مع إعادة تدوير مدمجة إضافية.
// SwiftUI — LazyVStack مع تحميل بطيء
ScrollView {
LazyVStack(spacing: 8) {
ForEach(articles) { article in
ArticleRow(article: article)
.onAppear {
if article == articles.last {
viewModel.loadMore()
}
}
}
}
}
التحميل البطيء لمكونات واجهة المستخدم هي تقنية حيث لا يتم إنشاء أجزاء من الواجهة (الرؤوس، التذييلات، أقسام الإعدادات، علامات التبويب) عند بدء تشغيل الشاشة، ولكن عند أول وصول إليها. هذا يسرع العرض الأولي ويقلل الحمل على الخيط الرئيسي.
ViewStub هو عنصر نائب خفيف للـ View في Android لا يشغل مساحة في التخطيط ولا ينشئ عروضاً فرعية حتى يتم استدعاء inflate(). مثالي للأقسام نادرة الاستخدام: لوحة البحث، الإعدادات المتقدمة، الكتل الإعلانية. التحميل البطيء للـ Fragment هو تقنية يتم فيها تأجيل Fragment.onCreateView حتى يتحول المستخدم إلى علامة التبويب هذه. يتم تنفيذه عبر isVisible أو UserVisibleHint في ViewPager.
AndroidX يوفر SplitInstallManager لتحميل الوحدات البطيء عند الطلب. يتم تحميل وحدات الإعدادات والتشخيص أو الوظائف الإضافية كوحدات Dynamic Feature فقط عند أول طلب من المستخدم. هذا يقلل الحجم الأساسي للتطبيق بنسبة 30–50% ويطبق في نفس الوقت مبدأ Lazy Loading ليس فقط على مستوى البيانات ولكن أيضاً على مستوى الكود.
TabView في SwiftUI يقوم بتحميل محتوى كل علامة تبويب بشكل بطيء — فقط عند تنشيط علامة التبويب. يقوم UIKit UITabBarController بإنشاء جميع وحدات التحكم الفرعية عند بدء التشغيل افتراضياً، ولكن يمكن تغيير هذا السلوك بعدم إضافتها إلى tabBarController.viewControllers فوراً واستبدالها أثناء تنقل المستخدم. UIStackView مع arrangedSubviews المضافة ديناميكياً يتبع أيضاً مبدأ Lazy Loading — أضف Subview فقط عندما يقوم المستخدم بإجراء يتطلب ذلك الجزء من الواجهة.
لـ تحسين تحميل الشاشة بشكل عام، قم بدمج Lazy Loading على جميع المستويات: ViewStub للأقسام نادرة الاستخدام، Paging 3 للبيانات، Glide/Coil للصور، والتهيئة البطيئة لـ ViewModel عبر Hilt/Dagger Scopes أو Swinject. هذا النهج يعطي شاشة يتم تحميلها في 200–400 ملي ثانية حتى على الأجهزة الاقتصادية بذاكرة وصول عشوائي 3 جيجابايت.
الأسئلة الشائعة
إذا كانت الشاشة تظهر بشكل مضمون عناصر قليلة (حتى 20) وكلها مطلوبة فوراً، فإن Lazy Loading غير ضروري. للقوائم التي نادراً ما يتم تمريرها، قد يكون Eager Loading أبسط وأسرع في التطبيق دون فقدان ملحوظ في الأداء.
يقلل استهلاك الذاكرة الأقصى بمقدار 3–10 مرات للقوائم الكبيرة، حيث يتم تخزين العناصر المرئية فقط بالإضافة إلى مخزن التحميل المسبق في الذاكرة. ومع ذلك، فإن إضافة التحميل المسبق والتخزين المؤقت للصور يخلق استهلاكاً معتدلاً للذاكرة يجب إدارته.
List مفضلة للبيانات المتجانسة مع دعم السحب والترتيب والتحديد المدمج. LazyVStack للتخطيطات المخصصة مع أنواع مختلفة من الخلايا والأقسام والمسافات غير القياسية. List تعمل داخلياً مثل LazyVStack مع وظائف إضافية.
على Android، استخدم Layout Inspector لعرض تسلسل العرض: مع Lazy Loading، يجب أن تكون معظم العناصر غائبة عن الشجرة. على iOS، استخدم Xcode Debug View Hierarchy. إذا كانت جميع العناصر موجودة أثناء التمرير، فإن Lazy Loading لا يعمل.
كلا المعيارين مهمان، ولكن الأولوية تعتمد على المنصة. على iOS مع ARC وإدارة الذاكرة الفعالة، الأولوية هي سرعة التحميل. على Android مع JVM و GC، الأولوية هي توفير الذاكرة، لأن كل تخصيص في الخيط الرئيسي يمكن أن يسبب تجميد GC.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا