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은 dequeueReusableCell과 함께 UITableView를 사용합니다. Android는 ViewHolder 풀과 함께 RecyclerView를 사용합니다. 데이터 수준에서는 Room with Paging 3 및 Core Data with NSFetchedResultsController가 있습니다. 특정 기술의 선택은 스택과 성능 요구 사항에 따라 달라집니다.
핵심 원칙은 현재 화면 상태에 필요한 정확한 데이터 양과 부드러운 스크롤을 위한 사전 버퍼를 로드하는 것입니다. 이 접근 방식은 가시성 추적과 요소 가상화라는 두 가지 메커니즘에 기반합니다.
추적 메커니즘은 어떤 요소가 화면의 가시 영역(뷰포트)에 있는지 결정합니다. Android에서는 LinearLayoutManager 또는 GridLayoutManager가 findFirstVisibleItemPosition 및 findLastVisibleItemPosition 메서드를 통해 이를 수행합니다. iOS에서는 UIScrollView가 가시 영역 계산을 위해 bounds.origin.y 및 contentOffset.height를 제공합니다. 요소가 뷰포트(또는 프리페치 버퍼)에 들어가면 로딩이 시작됩니다. 요소가 화면을 벗어나면 해당 리소스는 해제되거나 캐시로 이동될 수 있습니다.
// 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는 layoutManager.setItemPrefetchEnabled(true)를 통해 GapWorker.Prefetch를 지원합니다. iOS UITableView는 UITableViewDataSourcePrefetching을 통해 프리페칭을 지원합니다. 프리페치 버퍼 크기는 일반적으로 1–2 화선 앞으로 설정되어 스크롤 부드러움과 메모리 소비 간의 균형을 제공합니다. 너무 큰 프리페치 버퍼는 Lazy Loading의 이점을 무효화하고, 너무 작은 버퍼는 빠른 스크롤 시 빈 공간을 만듭니다.
이미지는 모바일 애플리케이션에서 가장 무거운 리소스 유형입니다. 단일 12MP 사진은 압축되지 않은 형태로 3–5MB를 차지할 수 있습니다. 이미지의 Lazy Loading은 수백 개의 보이지 않는 이미지가 메모리에 로드되는 것을 방지하며, 이는 사용자 아바타나 제품 카탈로그가 있는 리스트에 치명적입니다.
Glide는 캐싱, 변환 및 애니메이션을 지원하는 Android용 가장 인기 있는 이미지 로딩 라이브러리입니다. Coil은 코루틴을 사용하여 Kotlin으로 작성된 더 가벼운 대안입니다. 두 라이브러리 모두 ImageView가 화면을 벗어나면 자동으로 로딩을 일시 중지하고 ViewHolder가 재사용될 때 요청을 취소합니다. Coil은 코루틴을 사용하며 크기가 ~1.5MB로 Glide의 ~4MB에 비해 APK 크기에 중점을 둔 프로젝트에 선호됩니다.
// Coil — 지연 이미지 로딩
imageView.load("https://example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
size(512, 512)
memoryCachePolicy(CachePolicy.ENABLED)
}
Kingfisher는 Swift Concurrency, 디스크 및 메모리 캐싱, UICollectionView용 프리페칭을 지원하는 iOS 라이브러리입니다. SDWebImage는 Objective-C에 뿌리를 둔 더 오래된 라이브러리이지만 Swift를 지원합니다. 두 라이브러리 모두 UIImageView와 통합되어 로딩 수명 주기를 자동으로 관리합니다: 셀이 재사용될 때 요청을 취소하고, 셀이 보일 때만 이미지를 로드하며, 메모리 경고 알림 시 메모리를 해제합니다.
대규모 데이터 리스트는 모바일 앱에서 Lazy Loading의 주요 적용 분야입니다. 뉴스 피드, 제품 카탈로그, 채팅, 거래 내역 — 잠재적으로 무한한 리스트가 있는 모든 화면에는 페이지네이션과 지연 로딩이 필요합니다.
Paging 3은 Android Jetpack의 라이브러리로, RemoteMediator(API + 데이터베이스)에서 데이터 요청, Room에서 캐싱, PagingData를 통한 페이지별 출력, AsyncPagingDataAdapter를 통한 표시 등 완전한 지연 로딩 사이클을 구현합니다. Paging 3은 세 가지 유형의 페이지네이션을 지원합니다: 페이지 기반, 항목 기반(오프셋/제한), 키 기반(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 사용을 권장하며, List는 내부적으로 추가 내장 재활용 기능을 갖춘 LazyVStack처럼 작동합니다.
// SwiftUI — 지연 로딩을 사용한 LazyVStack
ScrollView {
LazyVStack(spacing: 8) {
ForEach(articles) { article in
ArticleRow(article: article)
.onAppear {
if article == articles.last {
viewModel.loadMore()
}
}
}
}
}
UI 컴포넌트의 지연 로딩은 인터페이스의 일부(헤더, 푸터, 설정 섹션, 탭)를 화면 시작 시가 아니라 첫 번째 액세스 시 생성하는 기술입니다. 이는 초기 렌더링을 가속화하고 메인 스레드의 부하를 줄입니다.
ViewStub은 Android의 경량 View 플레이스홀더로, 레이아웃에서 공간을 차지하지 않으며 inflate()가 호출될 때까지 하위 View를 생성하지 않습니다. 검색 패널, 고급 설정, 광고 블록과 같이 거의 사용되지 않는 섹션에 이상적입니다. Fragment 지연 로딩은 사용자가 해당 탭으로 전환할 때까지 Fragment.onCreateView를 지연하는 기술입니다. ViewPager에서 isVisible 또는 UserVisibleHint를 통해 구현됩니다.
AndroidX는 주문형 모듈의 지연 로딩을 위한 SplitInstallManager를 제공합니다. 설정, 진단 또는 추가 기능 모듈은 사용자의 첫 번째 요청 시에만 Dynamic Feature 모듈로 로드됩니다. 이는 기본 앱 크기를 30–50% 줄이고 동시에 데이터 수준뿐만 아니라 코드 수준에서도 Lazy Loading의 원칙을 구현합니다.
TabView는 SwiftUI에서 각 탭의 콘텐츠를 지연 로드합니다 — 탭이 활성화될 때만 로드됩니다. UIKit UITabBarController는 기본적으로 시작 시 모든 하위 컨트롤러를 생성하지만, 이를 즉시 tabBarController.viewControllers에 추가하지 않고 사용자가 전환할 때 대체하여 이 동작을 변경할 수 있습니다. 동적으로 추가된 arrangedSubviews가 있는 UIStackView도 Lazy Loading 원칙을 따릅니다 — 사용자가 인터페이스의 해당 부분이 필요한 작업을 수행할 때만 Subview를 추가합니다.
전체 화면 로딩을 최적화하려면 모든 수준에서 Lazy Loading을 결합하세요: 거의 사용되지 않는 섹션에는 ViewStub, 데이터에는 Paging 3, 이미지에는 Glide/Coil, Hilt/Dagger Scopes 또는 Swinject를 통한 지연 ViewModel 초기화. 이 접근 방식은 3GB RAM을 갖춘 저가형 기기에서도 200–400ms에 로드되는 화면을 제공합니다.
자주 묻는 질문
화면이 적은 수의 항목(최대 20개)만 표시하고 모두 즉시 필요한 경우 Lazy Loading은 과잉입니다. 스크롤이 거의 없는 리스트의 경우 Eager Loading이 눈에 띄는 성능 저하 없이 더 간단하고 빠르게 구현될 수 있습니다.
대규모 리스트의 경우 피크 메모리 소비를 3–10배 줄입니다. 보이는 요소와 프리페치 버퍼만 메모리에 저장되기 때문입니다. 그러나 프리페치와 이미지 캐싱을 추가하면 관리해야 할 적당한 메모리 오버헤드가 발생합니다.
List는 스와이프, 드래그 앤 드롭 및 내장 선택 지원이 있는 동질적인 데이터에 적합합니다. LazyVStack은 다양한 셀 유형, 섹션 및 비표준 간격이 있는 사용자 정의 레이아웃용입니다. List는 내부적으로 추가 기능을 갖춘 LazyVStack처럼 작동합니다.
Android에서는 Layout Inspector를 사용하여 View 계층 구조를 확인합니다: Lazy Loading을 사용하면 대부분의 요소가 트리에서 없어야 합니다. iOS에서는 Xcode Debug View Hierarchy를 사용합니다. 스크롤 중에 모든 요소가 존재하면 Lazy Loading이 작동하지 않는 것입니다.
두 매개변수 모두 중요하지만 우선순위는 플랫폼에 따라 다릅니다. ARC와 효율적인 메모리 관리를 갖춘 iOS에서는 로딩 속도가 우선입니다. JVM과 GC를 갖춘 Android에서는 메모리 절약이 우선입니다. 메인 스레드의 모든 할당이 GC 프리즈를 유발할 수 있기 때문입니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.