YunDaeHyeon
← Lab

List와 LazyVstack 성능 분석

실험2025.08.22
  • SwiftUI
  • UIKit
  • Swift

실험 주제

  1. List, LazyVStack, ScrollView + LazyVStack은 View Hierarchy 로 찍으면 어떤 차이가 있을까?
  2. 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

  • 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. 메모리 사용량

List

LazyVStack

ScrollView + LazyVStack

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이 있나 검색해보았다.

List 프로파일링 중 Cell에 dequeueReusableCell 을 검색했을 때 나오는 화면

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

2-2. ⭐ ScrollView + LazyVStack

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

List의 메모리 사용량

ScrollView + LazyVStack의 메모리 사용량

  • List의 경우 스크롤을 수행했을 때 메모리가 선형적으로 계속 증가하고 있었고
  • ScrollView + LazyVStack의 경우 스크롤을 수행했을 때 메모리가 6MB 안팎으로 유지되고 있었다.

🤔 그렇다면 ScrollView + LazyVStack도 Cell Tree에 UIKit dequeueReusableCell 가 있을까?

ScrollView + LazyVStack 프로파일링에서 Cell Tree에 검색한 모습

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

2-3. 번외. ScrollView를 쓰지 않은 LazyVStack

  • ScrollView를 쓰지 않은 LazyVStack은 단순히 화면에 보이는 부분만 렌더링되며 메모리 또한 화면에 보이는 부분만 할당된 것으로 보인다. (ContentRow가 42개, 메모리는 계속 약 4.9MB로 유지)

→ 사용할 일이 있나..? 싶다..


💡결론

  1. List, LazyVStack, ScrollView + LazyVStack은 View Hierarchy 로 찍으면 어떤 차이가 있을까?

→ List, LazyVStack, ScrollView + LazyVStack 모두 UIHostingController가 존재한다.

→ List에만 View Host가 존재하는 것으로 보아 SwiftUI와 UIKit을 하나로 이어주는 것으로 추측한다.

  1. List와 ScrollView + LazyVStack은 메모리 측면으로 어떤 차이가 있을까?

→ List 는 내부적으로 UIKit의 셀 재사용 매커니즘을 활용하지만 메모리에서 사라지지 않는 특성 상 대규모 데이터를 활용할 때 성능 저하가 유발되어 사용하기 적절치 않다.

→ ScrollView + LazyVStack 은 List와 달리 스크롤이 이뤄져도 메모리가 계속해서 유지되어 성능 측면에서 List보다 유리하다. 스크롤도 ScrollView + LazyVStack이 더 부드러운 것 같다.

→ ScrollView + LazyVStack은 내부적으로 셀을 재사용하지 않고 지연 로딩(Lazy Loading)을 통해 성능을 최적화한다.

⭐ List → 간단한 데이터 리스트, 정형화된 인터렉션이 필요한 경우에 쓰자.

⭐ ScrollView + LazyVStack → 대규모 데이터 리스트, 커스텀 레이아웃이 필요한 경우에 쓰자.