Lazy 스택으로 메모리를 챙겨보자

Taeyoung Won·2023년 5월 14일
1

SwiftUI

목록 보기
7/7
post-thumbnail

때는 SwiftUI를 정말 맨 처음 배울 때...

아마 다들 나처럼 Stack 3형제부터 배웠을 것이라고 생각한다. (V, H, Z 친구들)

어느 날 VStack을 만들다가 이상한 친구를 발견했다.

LazyVStack

이미 Swift 문법을 공부할 때 lazy var를 배웠기 때문에 비슷한 기능을 할 것이라고 바로 유추할 수 있었다.

'아 실제로 사용될 때, 지연 생성하는 친구겠구나'

그 당시에는 막 SwiftUI를 공부할 때였어서 이런게 있구나 하고 지나갔었다.

그러다가 최근에 렌더링 요소가 많은 스크롤뷰를 만들게 되면서, LazyVStack의 성능이 얼마나 효율적인지 궁금해졌다.

그러니까 한 번 확인해보자.




LazyVStack

늘 그랬듯 공식문서부터...

자식 뷰들이 수직 방향으로 쌓임 + 아이템들이 실제로 필요한 시점에 생성

위와 같은 특징을 가진 뷰라고 한다.

개요에서부터 100개의 Row 텍스트를 보여주는 예시를 설명하고 있다.

실제로 어떤 차이가 있는지 한 번 확인해보자.




생성 시점

struct RowView: View {
  let num: Int
  
  init(num: Int) {
    self.num = num
    print("Row \(num)")
  }
  
  var body: some View {
    Text("Row \(num)")
      .font(.title)
  }
}

Row에 해당하는 서브뷰인 RowView를 만들었다.

초기화되는 시점을 확인하기 위해, init 내부에서 print 하도록 했다.

차이를 확실하게 보기위해 아이템은 5,000개로 늘렸다.



VStack

우선 일반적인 VStack부터 확인해보자.

앱이 시작하자마자 5,000개가 모두 초기화되서 print가 출력됐다.



LazyVStack

이번엔 LazyVStack을 확인해보자.

Lazy는 구별을 위해 오렌지 색으로 칠해줬다.

아까와 달리 뷰에 표시되는 Row 21까지만 print가 출력됐다.

Row 21 이후로는 아직 화면에 표시되지 않았기 때문에 초기화를 지연시키고 있는 것이다.


스크롤을 조금 해보면 또 다른 특징을 볼 수 있다.

스크롤 하면서 새로운 Row가 표시될 때마다 새롭게 초기화되면서 print가 출력된다.

특히 스크롤뷰 하단을 보면 Row 50까지 화면에 나오지만, 실제로 초기화는 Row 71까지 된다.

스크롤 동작을 할 때 사용자에게 부드럽게 화면을 보여주기 위해서, 직후에 그려질 화면까지는 미리 초기화를 해두는 것을 알 수 있다.

딱 21개 차이가 나는 것을 보니 화면 사이즈에서 표시할 수 있는 Frame 범위만큼 추가적으로 렌더링 하는 것 같다.


그런데 이상하게 print가 두 번씩 출력된다.

찾아보니 SwiftUI에서 뷰 구성 및 렌더링 최적화 과정에서 재생성이 일어날 수 있다고 한다.

deinit으로 확인해보고 싶은데, 구조체에는 달 수가 없으니 메모리 체크용 class를 만들어서 deinit을 달고 프로퍼티로 넣어주었다.

struct RowView: View {
  let num: Int
  let memoryChecker: MemoryChecker
  
  init(num: Int) {
    print("Row \(num)")
    self.num = num
    self.memoryChecker = .init(num: num)
  }
  
  var body: some View {
    Text("Row \(num)")
      .font(.title)
  }
}

class MemoryChecker {
  let num: Int
  
  init(num: Int) {
    self.num = num
  }
  
  deinit {
    print("Row \(num) Gone")
  }
}

Row 하나당 생성 2회 해제 1회, 하나는 생성 후 즉시 해제되는 것 같다.




렌더링 속도

앱 실행 후 화면 렌더링까지 걸리는 시간을 녹화해봤는데, 나란히 두고 확인해보니 확실히 차이가 난다.

우리가 무한 스크롤을 구현하는 이유를 생각해보면, 성능적으로 확실히 효율적임은 확실하다.

유저가 이 뷰에 들어와서 최하단까지 전부 렌더링했는데, 스크롤을 안내리고 나가면 손해니까.




성능 효율

렌더링 시간 효율은 위에서 확인했으니, 실제로 CPU와 메모리에서 얼마나 차이가 나는지 확인해보자.

위쪽이 `VStack`, 아래쪽이 `LazyVStack`이다. 차이를 확실하게 보기 위해 5,000개로 세팅했지만, 메모리 사용량이 10배 이상 차이가난다.

예상 외였던 점은 처음에 한 번에 렌더링했기 때문에, 렌더링이 끝나면 VStack이 CPU 연산량은 더 적을 것이라고 생각했다.

LazyVStack은 실시간으로 계속 지연 렌더링에 대한 연산을 해야하기 때문이다.

그런데 반대로 VStack이 CPU 사용량도 4배정도 더 많았다.

실제로는 무한스크롤을 구현하기 때문에 이렇게 많은 아이템을 한번에 로드할 일은 잘 없겠지만, 잘 사용하면 굉장히 효율적인 컴포넌트라는 것을 데이터로 체감할 수 있는 기회였다.

profile
iOS Developer

0개의 댓글