일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Side Menu
- scrolling tab
- ViewBuilder
- GeometryReader
- oberve url
- url 추적
- notifychanged
- List
- 스크롤 탭
- DevelopmentRegion
- base64 변환
- transformation.map
- convert base64
- Swift Package Manager
- ios
- Android
- swift #swift keychain #keychain 사용법
- 상단 탭바
- 기존 앱
- swift
- 개발자 면접
- SwiftUI
- DataBinding
- detect url
- Tuist
- url 관찰
- development language
- pod install
- UIPresentationController
- UIViewControllerTransitioningDelegate
- Today
- Total
버그 잡이
SwiftUI 성능 이해 및 최적화 방법 알아보기 본문
SwiftUI를 쓰다보면 기존 AutoLayout 보다 성능이 떨어짐을 느낄 수 있습니다.
실제로 AutoLayout보다 메모리를 평균 20% 정도 더 차지하고(물론 어떤 뷰를 그리느냐에 따라 달라지겠지만)
이에 따라 성능상으로도 다소 떨어진다는 연구 결과도 있습니다.
이해 없이 SwiftUI를 쓰면 성능상 저하를 가져올 수 있기 때문에 SwiftUI는 어떤 식으로 비교 연산을 하고 어떻게 최적화 할 수 있을지 알아봅시다.
위 글(Mastering SwiftUI: Are you really as good as you think)을 보고 관련 내용을 정리한 글 입니다.
글쓴이는 글에 앞서 두 가지 질문을 던집니다.
"SwiftUI View가 왜 Struct인지 알고 있니?"
"SwiftUI View가 빌드하는 동안 init이 얼마나 호출되는지 알고 있니?"
SwiftUI의 View는 왜 struct인가?
SwiftUI는 생명주기 동안 view를 수없이 많이 다시 그립니다. (reconstruct)
그렇기 때문에 Class 보다 가벼운 Struct를 채택하는게 성능상에 이점이 있습니다.
* stack 메모리
* dynamic dispatch
SwiftUI는 왜 View를 다시 그리나?
Reconstruct는 State가 변할때 시작됩니다.
단, State만 바뀌고 그 State가 View에 적용되지 않았을때는 View가 재구성 되지 않습니다.
*TIP
View의 body 안에 아래 코드를 추가하면 view 안에 있는 state가 바뀌었는지 로그로 확인해볼 수 있습니다.
let _ = Self.printChanges()
@State 대신에 @ObservableObject도 자주 쓰는데요.
ObservableObject는 State와 다르게 view에 적용되지 않았을때도 view를 다시 그립니다.
이러한 이슈를 개선해서 나온 것이 @Observable입니다.
@Observable은 @State와 비슷하게 동작하기 때문에 바뀐 상태가 view에 적용되어 있지 않으면 body를 다시 호출하지 않습니다.
(단, iOS 17부터 사용 가능하네요.. ㅜ)
SwiftUI가 왜 struct로 되어있는지 View는 언제 다시 그려지는지 알아보았는데요.
다음으로 SwiftUI가 어떻게 View를 다시 그리는지 그 과정을 좀 더 구체적으로 알아보겠습니다.
SwiftUI와 Reconstruct 그 세부 과정
상태가 변경되었을때 SwiftUI가 하는 작업의 순서는 아래와 같습니다.
1. 새로운 상태와 기존 상태를 비교
2. 상태가 다르면 body 호출하여 새로운 뷰를 생성 후 기존 뷰와 비교
3. 새로운 뷰와 기존 뷰를 비교해서 다르면 새로운 뷰를 뷰 트리에 추가
이 원리에서 우리가 최적화 할 수 있는 방법을 찾을 수 있습니다.
1. body 내부를 깔끔하게 할 것
- body 내부에 불필요한 연산이 포함되어 있다면, View를 비교하는 과정에서 매번 리소스를 잡아먹게 됩니다.
2. body 안에 있는 View들을 ChildView로 쪼개서 관리
- 앞서 말했듯이 상태가 바뀌지 않으면 SwiftUI는 View를 비교하지 않습니다.
- 상태가 바뀐 하위 뷰만 다시 그려질 겁니다.
- 그렇기 때문에 상태와 연관이 있는 뷰 끼리 모아서 쪼개는 것이 좋습니다.
import SwiftUI
// 상태와 관련된 하위 뷰
struct ChildView: View {
@Binding var counter: Int
var body: some View {
VStack {
Text("Counter: \(counter)")
Button(action: {
counter += 1
}) {
Text("Increment")
}
}
}
}
// 메인 뷰
struct ContentView: View {
@State private var counter: Int = 0
@State private var otherState: String = "Initial State"
var body: some View {
VStack {
// 상태와 연관된 하위 뷰
ChildView(counter: $counter)
// 상태와 무관한 다른 뷰
Text("Other State: \(otherState)")
Button(action: {
otherState = "State Changed"
}) {
Text("Change Other State")
}
}
}
}
SwiftUI View가 비교하는 Method
SwiftUI가 View를 새로 만들고 기존 View 비교하는 연산을 한다고 설명했는데요.
Views들을 비교할때 SwiftUI는 3가지 방법을 사용한다고 합니다.
- memcmp (가장 빠름)
- Equality (조금 느림)
- Reflection (느림)
위부터 아래로 갈수록 성능이 떨어진다고 합니다.
이를 위해서 POD(Plain Old Data)라는 용어를 이해할 필요가 있는데요.
array, string, @State와 같은 value semantic의 상태 변수를 포함한 View Struct는 Non-POD 객체고
int, bool 같이 순수한 View Struct는 POD 객체입니다.
근데 이게 좀 불명확해서 print(_isPOD(ContentView.self)) 로 찍어보는게 정확하다고 합니다.
POD에 대해서는 이 정도까지만 알아보고
"그래서 어떻게 비교 성능을 올릴건데?" 라는 질문에 답하면
POD View는 memcmp를 사용하고
Non-POD View는 Reflection을 사용한다고 합니다. (단, Equatable을 재택하면 Equality를 사용)
그래서 Non-POD View를 쓸때는 Equatable을 채택하자. 이겁니다.
세 줄 요약
1. 비교를 최소화 하자
- ObservableObject 대신 @Observable 사용하기
2. body를 최적화 하자
- body에서 불필요한 연산 제거
- Child View를 분리해서 불필요한 View reconstruct 제거
3. 비교 성능을 최적화 하자.
- Non-POD View일때는 Equatable을 채택
'SwiftUI' 카테고리의 다른 글
SwiftUI - ObservableObject vs StateObject (1) | 2024.06.16 |
---|---|
SwiftUI - AnyView란 무엇인가? (0) | 2024.06.15 |
SwiftUI List 기본 스타일 지우기 (0) | 2024.03.01 |
SwiftUI - List에서 원하는 Row로 스크롤 하기 #scrollTo (0) | 2023.07.29 |
SwiftUI - scrollIndicators() 로 textEditor 스크롤바 hidden 처리하기 (0) | 2023.06.18 |