Lazy Loading در توسعه موبایل — نحوه عملکرد، اصول و پیاده‌سازی

نویسنده: IT Sectr منتشر شده: 2026-04-01 زمان مطالعه: 9 دقیقه

Lazy Loading — استراتژی بارگذاری تأخیری داده‌ها، تصاویر و کامپوننت‌ها است که در آن منابع نه هنگام راه‌اندازی برنامه، بلکه در لحظه‌ای که کاربر واقعاً به آنها نیاز دارد درخواست می‌شوند. طبق Android Paging 3 Guide، بارگذاری تنبل لیست‌ها مصرف حافظه را هنگام کار با مجموعه داده‌های بزرگ 60–80٪ کاهش می‌دهد. مقداردهی تأخیری — اصل کلیدی است که زیربنای تمام پیاده‌سازی‌های Lazy Loading است.

نکات اصلی

  • Lazy Loading — الگوی بارگذاری تأخیری که در حافظه صرفه‌جویی کرده و راه‌اندازی اولیه را سرعت می‌بخشد
  • LazyVStack و LazyHStack — کامپوننت‌های داخلی SwiftUI برای لیست‌های تنبل
  • Paging 3 — کتابخانه Android برای بارگذاری صفحه‌بندی شده داده‌ها از API و پایگاه داده
  • کتابخانه‌های تصویر (Glide, Coil, Kingfisher) تصاویر را فقط هنگام ظاهر شدن روی صفحه بارگذاری می‌کنند
  • Preloading — بارگذاری پیش‌دستانه، روی دیگر سکه Lazy Loading برای UX روان

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) می‌شود، بارگذاری آن آغاز می‌شود. وقتی عنصر صفحه را ترک می‌کند، منابع آن می‌توانند آزاد یا به کش منتقل شوند.

kotlin
// 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

بافر Prefetch — بارگذاری پیش‌دستانه عناصری که به زودی روی صفحه ظاهر می‌شوند. RecyclerView از GapWorker.Prefetch از طریق layoutManager.setItemPrefetchEnabled(true) پشتیبانی می‌کند. iOS UITableView از prefetching از طریق UITableViewDataSourcePrefetching پشتیبانی می‌کند. اندازه بافر prefetch معمولاً 1–2 صفحه جلوتر است که مصالحه‌ای بین روانی اسکرول و مصرف حافظه ایجاد می‌کند. بیش از حد بزرگ بودن بافر prefetch مزایای Lazy Loading را از بین می‌برد، بیش از حد کوچک بودن — در اسکرول سریع مکان‌های خالی ایجاد می‌کند.

بارگذاری تنبل تصاویر

تصاویر — سنگین‌ترین نوع منابع در برنامه‌های موبایل هستند. یک عکس 12 مگاپیکسلی می‌تواند 3–5 مگابایت در حالت فشرده‌نشده اشغال کند. بارگذاری تنبل تصاویر از بارگذاری صدها تصویر نامرئی در حافظه جلوگیری می‌کند که برای لیست‌های نظرات کاربران یا کاتالوگ محصولات فاجعه‌بار خواهد بود.

کتابخانه‌های Android: Glide و Coil

Glide — محبوب‌ترین کتابخانه بارگذاری تصویر برای Android با پشتیبانی از کش‌کردن، تبدیل و انیمیشن. Coil — جایگزین سبک‌تری که با استفاده از کوروتین‌ها به زبان Kotlin نوشته شده است. هر دو کتابخانه به طور خودکار بارگذاری را وقتی ImageView صفحه را ترک می‌کند متوقف کرده و درخواست‌ها را هنگام استفاده مجدد از ViewHolder لغو می‌کنند. Coil از کوروتین‌ها استفاده می‌کند و اندازه آن ~1.5 مگابایت در مقابل ~4 مگابایت Glide است که آن را برای پروژه‌های متمرکز بر اندازه APK ترجیح‌پذیر می‌کند.

kotlin
// Coil — بارگذاری تنبل تصویر
imageView.load("https://example.com/image.jpg") {
    crossfade(true)
    placeholder(R.drawable.placeholder)
    size(512, 512)
    memoryCachePolicy(CachePolicy.ENABLED)
}

کتابخانه‌های iOS: Kingfisher و SDWebImage

Kingfisher — کتابخانه iOS با پشتیبانی از Swift Concurrency، کش روی دیسک و حافظه و همچنین prefetching برای UICollectionView. SDWebImage — کتابخانه قدیمی‌تر با ریشه‌های Objective-C اما با پشتیبانی از Swift. هر دو کتابخانه با UIImageView ادغام شده و به طور خودکار چرخه عمر بارگذاری را مدیریت می‌کنند: درخواست‌ها را هنگام استفاده مجدد از سلول لغو می‌کنند، تصاویر را فقط وقتی سلول قابل مشاهده است بارگذاری می‌کنند و هنگام اعلان کمبود حافظه، حافظه را آزاد می‌کنند.

بارگذاری تنبل داده‌ها و لیست‌ها

لیست‌های بزرگ داده — حوزه اصلی کاربرد Lazy Loading در برنامه‌های موبایل. فید خبری، کاتالوگ محصولات، چت‌ها، تاریخچه عملیات — هر صفحه با لیست بالقوه بی‌نهایت نیاز به صفحه‌بندی و بارگذاری تأخیری دارد.

Paging 3 برای Android

Paging 3 — کتابخانه‌ای از Android Jetpack که چرخه کامل بارگذاری تنبل را پیاده‌سازی می‌کند: درخواست داده از RemoteMediator (API + پایگاه داده)، کش در Room، تحویل صفحه‌بندی شده از طریق PagingData و نمایش از طریق AsyncPagingDataAdapter. Paging 3 از سه نوع صفحه‌بندی پشتیبانی می‌کند: Page-based (صفحات)، Item-based (offset/limit) و Key-based (کلیدهای صفحه‌بندی از API). Separator — پشتیبانی داخلی از جداکننده‌های بین صفحات برای نشانگرهای بارگذاری.

kotlin
// 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)
        }
    }
}

SwiftUI LazyVStack و LazyHStack

LazyVStack — کانتینر داخلی SwiftUI که عناصر را فقط هنگام ظاهر شدن روی صفحه ایجاد و رندر می‌کند. برخلاف VStack که layout تمام عناصر فرزند را فوراً محاسبه می‌کند، LazyVStack ایجاد ویو را تا زمانی که عنصر قابل مشاهده شود یا وارد محدوده prefetch شود به تأخیر می‌اندازد. LazyHStack — مشابه افقی برای کاروسل‌ها. برای لیست‌های حجیم، Apple استفاده از List را توصیه می‌کند که در داخل مشابه LazyVStack با بازیافت داخلی اضافه کار می‌کند.

swift
// SwiftUI — LazyVStack با بارگذاری تنبل
ScrollView {
    LazyVStack(spacing: 8) {
        ForEach(articles) { article in
            ArticleRow(article: article)
                .onAppear {
                    if article == articles.last {
                        viewModel.loadMore()
                    }
                }
        }
    }
}

بارگذاری تنبل کامپوننت‌های UI

بارگذاری تنبل کامپوننت‌های UI — تکنیکی که در آن بخش‌هایی از رابط (هدرها، فوترها، بخش‌های تنظیمات، تب‌ها) نه هنگام راه‌اندازی صفحه، بلکه در اولین مراجعه به آنها ایجاد می‌شوند. این کار رندر اولیه را سرعت می‌بخشد و بار روی main thread را کاهش می‌دهد.

Android: ViewStub و بارگذاری تنبل Fragment

ViewStub — جای‌گیرنده سبک View در Android که در layout جا نمی‌گیرد و Viewهای فرزند را تا فراخوانی inflate() ایجاد نمی‌کند. ایده‌آل برای بخش‌های کم‌استفاده: پنل جستجو، تنظیمات پیشرفته، بلوک‌های تبلیغاتی. بارگذاری تنبل Fragment — تکنیکی که در آن Fragment.onCreateView تا زمانی که کاربر به آن تب سوئیچ نکند به تأخیر می‌افتد. از طریق isVisible یا UserVisibleHint در ViewPager پیاده‌سازی می‌شود.

AndroidX SplitInstallManager را برای بارگذاری تنبل ماژول‌ها بر اساس تقاضا فراهم می‌کند. ماژول‌های تنظیمات، تشخیص یا عملکردهای اضافی فقط در اولین درخواست کاربر به عنوان ماژول‌های Dynamic Feature بارگذاری می‌شوند. این کار اندازه پایه برنامه را 30–50٪ کاهش می‌دهد و همزمان اصل Lazy Loading را نه تنها در سطح داده بلکه در سطح کد نیز پیاده‌سازی می‌کند.

iOS: TabView و تنبلی ViewBuilder

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 استفاده کرد؟

اگر صفحه به طور تضمینی تعداد کمی عنصر (تا ۲۰) را نشان می‌دهد و همه آنها فوراً نیاز هستند — Lazy Loading اضافی است. برای لیست‌هایی که به ندرت اسکرول می‌شوند، Eager Loading می‌تواند بدون افت عملکرد قابل توجه، در پیاده‌سازی ساده‌تر و سریع‌تر باشد.

Lazy Loading چگونه بر حافظه تأثیر می‌گذارد؟

مصرف حافظه اوج را برای لیست‌های بزرگ ۳–۱۰ برابر کاهش می‌دهد، زیرا در حافظه فقط عناصر قابل مشاهده به همراه بافر prefetch ذخیره می‌شوند. با این حال، افزودن prefetch و کش کردن تصاویر مصرف متعادلی از حافظه ایجاد می‌کند که باید مدیریت شود.

چه چیزی را انتخاب کنم — LazyVStack یا List در SwiftUI؟

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 شود.

خلاصه

  • Lazy Loading — الگوی بارگذاری تأخیری که راه‌اندازی اولیه را سرعت بخشیده و در حافظه صرفه‌جویی می‌کند
  • تصاویر از طریق Glide, Coil, Kingfisher فقط هنگام ورود به viewport بارگذاری می‌شوند
  • Paging 3 برای Android بارگذاری صفحه‌بندی شده داده‌ها با کش در Room را فراهم می‌کند
  • LazyVStack در SwiftUI رندر عناصر را تا ظاهر شدن روی صفحه به تأخیر می‌اندازد
  • ViewStub در Android — بارگذاری تنبل کامپوننت‌های UI در اولین مراجعه
  • بافر Prefetch — بارگذاری پیش‌دستانه برای اسکرول روان
  • Lazy Loading را در همه سطوح ترکیب کنید: داده‌ها + تصاویر + UI

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید