List와 LazyVstack 성능 분석
- SwiftUI
- UIKit
- Swift
실험 주제
- List, LazyVStack, ScrollView + LazyVStack은
View Hierarchy로 찍으면 어떤 차이가 있을까? - List와 ScrollView + LazyVStack은
메모리측면으로 어떤 차이가 있을까?
- List는 내부에 정의된 Row를 모두 랜더링 한다.
- 단, 내부적으로 셀을 재사용한다는데.. 진짜일까?
- LazyVStack은 화면에 보이는 부분만 렌더링을 진행한다는데.. 진짜일까?
- LazyVStack과 ScrollView + LazyVStack은 어떤 차이가 있을까?
📌 첫 번째 실험 : Hierarchy
- 공통적으로 사용한 코드는 아래와 같으며
List만 변경하는 식으로 진행했다.
struct ContentRow: View {
let id: Int
init(id: Int) {
print("\(id)번째 Row가 생성되었습니다.")
self.id = id
}
var body: some View {
Text("\(id)번째 Row")
}
}
struct ContentView: View {
var body: some View {
List {
ForEach(1...100, id: \.self, content: ContentRow.init)
}
}
}
- List의 경우 View Hierarchy로 찍어보면 다음과 같이 출력되고 있다.

- 가장 많이 보이는 것은
ListCollectionViewCell인것으로 보아 UICollectionViewCell를 사용하는 듯한 구조를 가지고 있다. - 또한 특이한 점이
Hosting View Controller와 동시에 View Host라는 것이 보이는데 찾아보니 아래와 같다고 한다.

- SwiftUI 뷰를 UIKit 뷰 계층 구조에 통합하기 위해 사용된다고 명시되어있는 것으로 보아
SwiftUI의 List는 내부적으로 UIKit로 구성되어 있구나!하고 추측했다.
UIHostingController는 SwiftUI View의 Hierarchy를 관리하는 UIKit View Controller이다.
- 그렇다면 이것을 이용해 SwiftUI와 UIKit을 혼용해서 사용할 수 있구나! 라는 걸 다시금 느낀다.
2. LazyVStack

- LazyVStack의 경우 View Hierarchy를 찍어본 결과
View Host하위에ListCollectionViewCell가 보이는 List와 달리LazyVStackLayout으로 감싸진PlaceholderContentView라는 것이 다수 보이고 있다.
3. ScrollView + LazyVStack
- 그렇다면 ScrollView로 감싼 LazyVStack은 어떤 차이가 있을까?

- 별다른 특이점 없이
LazyVStack을ScrollView가 감싸고 있는 구조로 보인다.
🤔 정리



- List, LazyVStack, ScrollView + LazyVStack 모두
UIHostingController가 존재한다.
→ 3개 모두 UIKit과 관련이 있다.
- List에만
View Host가 존재한다.
→ SwiftUI에서 사용되는 List를 UIKit 계층 안에서 사용할 수 있도록 해주는 브릿지인건가..? 하는 추측이다.
너무 헷갈린다.
📌 두 번째 실험 : 메모리
공통 : ContentRow를 1000개 생성했을 경우 메모리를 얼마나 점유하는가?
대상 기기 : iPhone 15 Pro (iOS 18.1)
0. 공통 사용 코드
struct ContentRow: View {
let id: Int
var body: some View {
Text("\(id)번째 Row")
}
}
struct ContentView: View {
var body: some View {
List {
ForEach(1...1000, id: \.self, content: ContentRow.init)
}
}
}
1. 메모리 사용량



2. Instrument를 통해 직접 프로파일링 해보기
사용 탬플릿 : SwiftUI (View Body), Time Profiler, Allcations
2-1. List

- 최초 앱 실행 시 화면에 보이는 18번째 row (ContentRow의 Count가 18로 시작)가 먼저 생성이 되며 스크롤 시 메모리와 Row 모두 선형적으로 증가하고 있음을 확인했다.
- 만약 스크롤을 아래로 끝까지 내렸다가 다시 위로 끝까지 올렸을 경우

- 위 이미지와 같이 ContentRow의 Count는 계속 증가하고 있었고 메모리 또한 선형적으로 무한 증가하고 있었다.
- 이를 통해 List의 경우 사용자가 스크롤을 내리면 화면 바깥으로 사라지는 요소
메모리에서 사라지지 않는다.라는 걸 알 수 있다. - 이 때문에 방대한 데이터를
List를 통해 화면에 그리면 메모리 사용량이 너무 커져 앱이 멈추거나 종료될 수 있다고 느껴졌다.
🤔 의문. 진짜 List는 내부적으로 UIKit의 셀 재사용 기법을 사용하나?
메모리에서 사라지지 않는다는 것은 알았으며 대규모 데이터셋에 사용하지 적합하지 않았다는 것은 알았지만.. 진짜 셀을 재사용하는지 궁금했다.
혹시나 하는 마음에 Cell Tree에서 dequeueReusableCell이 있나 검색해보았다.

- 아니나 다를까 분명 SwiftUI의 List를 사용했지만 내부적으로 익숙한 UICollectionView의 셀 재사용 함수로 보이는
dequeueReusableCell가 보였다. - 오.. 정말 SwiftUI의 List는 내부적으로 UIKit의 재사용 셀 매커니즘을 활용것으로 보인다.
2-2. ⭐ ScrollView + LazyVStack

- ScrollVIew + LazyVStack의 경우 List와 같이 최초 앱 실행 시 ContentRow의 Count는 화면에 보이는 Row의 개수만큼 38로 시작이 되고 있다.
- 또한 List와 같이 스크롤을 맨 아래로 내린 뒤, 다시 위로 올린 결과
메모리 측면에서 차이가 확실히 있었다.


- List의 경우 스크롤을 수행했을 때 메모리가 선형적으로 계속
증가하고있었고 - ScrollView + LazyVStack의 경우 스크롤을 수행했을 때 메모리가 6MB 안팎으로
유지되고 있었다.
🤔 그렇다면 ScrollView + LazyVStack도 Cell Tree에 UIKit dequeueReusableCell 가 있을까?

- 신기하게도 List와 달리
UIKitCore나UIKit관련 용어는 단 하나도 없었다. - 오직
SwiftUI나SwiftUICore만 나오며 LazyLayout 용어가 계속 나오는 것으로 보아 SwiftUI 만의 독자적인 지연 로딩 기법으로 성능을 최적화하는 것으로 추측이 된다! - 이로써 List는 정말 UIKit의 Cell 재사용 매커니즘을 사용한다는 것을 알 수 있었다.
2-3. 번외. ScrollView를 쓰지 않은 LazyVStack

- ScrollView를 쓰지 않은 LazyVStack은 단순히 화면에 보이는 부분만 렌더링되며 메모리 또한 화면에 보이는 부분만 할당된 것으로 보인다. (ContentRow가 42개, 메모리는 계속 약 4.9MB로 유지)
→ 사용할 일이 있나..? 싶다..
💡결론
- List, LazyVStack, ScrollView + LazyVStack은
View Hierarchy로 찍으면 어떤 차이가 있을까?
→ List, LazyVStack, ScrollView + LazyVStack 모두 UIHostingController가 존재한다.
→ List에만 View Host가 존재하는 것으로 보아 SwiftUI와 UIKit을 하나로 이어주는 것으로 추측한다.
- List와 ScrollView + LazyVStack은
메모리측면으로 어떤 차이가 있을까?
→ List 는 내부적으로 UIKit의 셀 재사용 매커니즘을 활용하지만 메모리에서 사라지지 않는 특성 상 대규모 데이터를 활용할 때 성능 저하가 유발되어 사용하기 적절치 않다.
→ ScrollView + LazyVStack 은 List와 달리 스크롤이 이뤄져도 메모리가 계속해서 유지되어 성능 측면에서 List보다 유리하다. 스크롤도 ScrollView + LazyVStack이 더 부드러운 것 같다.
→ ScrollView + LazyVStack은 내부적으로 셀을 재사용하지 않고 지연 로딩(Lazy Loading)을 통해 성능을 최적화한다.
⭐ List → 간단한 데이터 리스트, 정형화된 인터렉션이 필요한 경우에 쓰자.
⭐ ScrollView + LazyVStack → 대규모 데이터 리스트, 커스텀 레이아웃이 필요한 경우에 쓰자.