Lazy Loading — استراتژی بارگذاری تأخیری دادهها، تصاویر و کامپوننتها است که در آن منابع نه هنگام راهاندازی برنامه، بلکه در لحظهای که کاربر واقعاً به آنها نیاز دارد درخواست میشوند. طبق Android Paging 3 Guide، بارگذاری تنبل لیستها مصرف حافظه را هنگام کار با مجموعه دادههای بزرگ 60–80٪ کاهش میدهد. مقداردهی تأخیری — اصل کلیدی است که زیربنای تمام پیادهسازیهای Lazy Loading است.
نکات اصلی
Lazy Loading (بارگذاری تنبل، بارگذاری تأخیری) — الگوی طراحی و بهینهسازی است که در آن منابع برنامه نه هنگام راهاندازی، بلکه مستقیماً قبل از استفاده بارگذاری میشوند. در توسعه موبایل، Lazy Loading برای سه دسته اصلی اعمال میشود: دادهها (صفحهبندی لیستها)، تصاویر (بارگذاری هنگام اسکرول) و کامپوننتها (استکها و ویوهای تنبل).
نقطه مقابل Lazy Loading Eager Loading (بارگذاری حریصانه) نام دارد، زمانی که همه منابع هنگام راهاندازی صفحه بارگذاری میشوند. Eager Loading در پیادهسازی سادهتر است، اما حافظه بیشتری مصرف میکند و زمان اولین نمایش را افزایش میدهد. برای لیستهایی با هزاران عنصر، Eager Loading در دستگاههای با حافظه محدود منجر به OOM (Out of Memory) میشود. 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 (یا بافر prefetch) میشود، بارگذاری آن آغاز میشود. وقتی عنصر صفحه را ترک میکند، منابع آن میتوانند آزاد یا به کش منتقل شوند.
// 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
// صفحه بعدی را بارگذاری میکنیم اگر کمتر از ۵ عنصر باقی مانده باشد < ۵ عنصر
if (lastVisible >= totalCount - 5) {
loadMoreItems()
}
}
})
بافر Prefetch — بارگذاری پیشدستانه عناصری که به زودی روی صفحه ظاهر میشوند. RecyclerView از GapWorker.Prefetch از طریق layoutManager.setItemPrefetchEnabled(true) پشتیبانی میکند. iOS UITableView از prefetching از طریق UITableViewDataSourcePrefetching پشتیبانی میکند. اندازه بافر prefetch معمولاً 1–2 صفحه جلوتر است که مصالحهای بین روانی اسکرول و مصرف حافظه ایجاد میکند. بیش از حد بزرگ بودن بافر prefetch مزایای Lazy Loading را از بین میبرد، بیش از حد کوچک بودن — در اسکرول سریع مکانهای خالی ایجاد میکند.
تصاویر — سنگینترین نوع منابع در برنامههای موبایل هستند. یک عکس 12 مگاپیکسلی میتواند 3–5 مگابایت در حالت فشردهنشده اشغال کند. بارگذاری تنبل تصاویر از بارگذاری صدها تصویر نامرئی در حافظه جلوگیری میکند که برای لیستهای نظرات کاربران یا کاتالوگ محصولات فاجعهبار خواهد بود.
Glide — محبوبترین کتابخانه بارگذاری تصویر برای Android با پشتیبانی از کشکردن، تبدیل و انیمیشن. Coil — جایگزین سبکتری که با استفاده از کوروتینها به زبان Kotlin نوشته شده است. هر دو کتابخانه به طور خودکار بارگذاری را وقتی ImageView صفحه را ترک میکند متوقف کرده و درخواستها را هنگام استفاده مجدد از ViewHolder لغو میکنند. Coil از کوروتینها استفاده میکند و اندازه آن ~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، کش روی دیسک و حافظه و همچنین prefetching برای UICollectionView. SDWebImage — کتابخانه قدیمیتر با ریشههای Objective-C اما با پشتیبانی از Swift. هر دو کتابخانه با UIImageView ادغام شده و به طور خودکار چرخه عمر بارگذاری را مدیریت میکنند: درخواستها را هنگام استفاده مجدد از سلول لغو میکنند، تصاویر را فقط وقتی سلول قابل مشاهده است بارگذاری میکنند و هنگام اعلان کمبود حافظه، حافظه را آزاد میکنند.
لیستهای بزرگ داده — حوزه اصلی کاربرد Lazy Loading در برنامههای موبایل. فید خبری، کاتالوگ محصولات، چتها، تاریخچه عملیات — هر صفحه با لیست بالقوه بینهایت نیاز به صفحهبندی و بارگذاری تأخیری دارد.
Paging 3 — کتابخانهای از Android Jetpack که چرخه کامل بارگذاری تنبل را پیادهسازی میکند: درخواست داده از RemoteMediator (API + پایگاه داده)، کش در Room، تحویل صفحهبندی شده از طریق PagingData و نمایش از طریق AsyncPagingDataAdapter. Paging 3 از سه نوع صفحهبندی پشتیبانی میکند: Page-based (صفحات)، Item-based (offset/limit) و Key-based (کلیدهای صفحهبندی از 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 که layout تمام عناصر فرزند را فوراً محاسبه میکند، LazyVStack ایجاد ویو را تا زمانی که عنصر قابل مشاهده شود یا وارد محدوده prefetch شود به تأخیر میاندازد. LazyHStack — مشابه افقی برای کاروسلها. برای لیستهای حجیم، Apple استفاده از List را توصیه میکند که در داخل مشابه LazyVStack با بازیافت داخلی اضافه کار میکند.
// SwiftUI — LazyVStack با بارگذاری تنبل
ScrollView {
LazyVStack(spacing: 8) {
ForEach(articles) { article in
ArticleRow(article: article)
.onAppear {
if article == articles.last {
viewModel.loadMore()
}
}
}
}
}
بارگذاری تنبل کامپوننتهای UI — تکنیکی که در آن بخشهایی از رابط (هدرها، فوترها، بخشهای تنظیمات، تبها) نه هنگام راهاندازی صفحه، بلکه در اولین مراجعه به آنها ایجاد میشوند. این کار رندر اولیه را سرعت میبخشد و بار روی main thread را کاهش میدهد.
ViewStub — جایگیرنده سبک View در Android که در layout جا نمیگیرد و Viewهای فرزند را تا فراخوانی 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. چنین رویکردی صفحهای را میدهد که حتی در دستگاههای ارزانقیمت با 3 گیگابایت رم در 200–400 میلیثانیه بارگذاری میشود.
سوالات متداول
اگر صفحه به طور تضمینی تعداد کمی عنصر (تا ۲۰) را نشان میدهد و همه آنها فوراً نیاز هستند — Lazy Loading اضافی است. برای لیستهایی که به ندرت اسکرول میشوند، Eager Loading میتواند بدون افت عملکرد قابل توجه، در پیادهسازی سادهتر و سریعتر باشد.
مصرف حافظه اوج را برای لیستهای بزرگ ۳–۱۰ برابر کاهش میدهد، زیرا در حافظه فقط عناصر قابل مشاهده به همراه بافر prefetch ذخیره میشوند. با این حال، افزودن prefetch و کش کردن تصاویر مصرف متعادلی از حافظه ایجاد میکند که باید مدیریت شود.
List برای دادههای همگن با قابلیت سوایپ، کشیدن و انتخاب داخلی ترجیح داده میشود. LazyVStack — برای layoutهای سفارشی با انواع مختلف سلولها، بخشها و فاصلههای غیراستاندارد. List در داخل مانند LazyVStack با قابلیتهای اضافی کار میکند.
در Android از Layout Inspector برای مشاهده سلسلهمراتب View استفاده کنید: در Lazy Loading بیشتر عناصر باید در درخت وجود نداشته باشند. در iOS — Xcode Debug View Hierarchy. اگر هنگام اسکرول همه عناصر وجود دارند — Lazy Loading کار نمیکند.
هر دو پارامتر مهم هستند، اما اولویت به پلتفرم بستگی دارد. در iOS با ARC و مدیریت کارآمد حافظه، اولویت سرعت بارگذاری است. در Android با JVM و GC، اولویت صرفهجویی در حافظه است، زیرا هر تخصیص در main thread میتواند باعث GC-friz شود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید