때는 SwiftUI를 정말 맨 처음 배울 때...
아마 다들 나처럼 Stack
3형제부터 배웠을 것이라고 생각한다. (V, H, Z 친구들)
어느 날 VStack
을 만들다가 이상한 친구를 발견했다.
LazyVStack
이미 Swift 문법을 공부할 때 lazy var를 배웠기 때문에 비슷한 기능을 할 것이라고 바로 유추할 수 있었다.
'아 실제로 사용될 때, 지연 생성하는 친구겠구나'
그 당시에는 막 SwiftUI를 공부할 때였어서 이런게 있구나 하고 지나갔었다.
그러다가 최근에 렌더링 요소가 많은 스크롤뷰를 만들게 되면서, 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
부터 확인해보자.
앱이 시작하자마자 5,000개가 모두 초기화되서 print
가 출력됐다.
이번엔 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
이 CPU 연산량은 더 적을 것이라고 생각했다.
LazyVStack
은 실시간으로 계속 지연 렌더링에 대한 연산을 해야하기 때문이다.
그런데 반대로 VStack
이 CPU 사용량도 4배정도 더 많았다.
실제로는 무한스크롤을 구현하기 때문에 이렇게 많은 아이템을 한번에 로드할 일은 잘 없겠지만, 잘 사용하면 굉장히 효율적인 컴포넌트라는 것을 데이터로 체감할 수 있는 기회였다.