본문 바로가기

SwiftUI

SwiftUI View 성능 향상을 위한 종속성 최소화하기

반응형

올해 WWDC23에 나온 영상을 보고 SwiftUI의 종속성, 그리고 퍼포먼스에 대해 고민을 하기 시작했다. 

 

SwiftUI는 구현이 UIKit보다 간편하게 구현할 수 있다는 장점이 있다. 하지만 그간 View의 성능이 향상되는 것에 대한 고민은 해보지 못했던 것 같다. 

어떻게 성능향상을 이뤄낼 수 있을까?

SwiftUI View는 기본적으로 데이터가 변화하면 화면을 다시 그리는 작업을 수행한다. 

때문에 개발자는 어떤 데이터가 바뀐 뷰만 업데이트하고 싶을지 몰라도 데이터와 연결(종속 관계에 있는)된 다른 뷰가 업데이트 될 수 있다. 

이러면 성능의 저하를 불러 일으킬 수 있다. 따라서 업데이트 횟수를 줄이는 것을 항상 염두해 두어야 한다. 

 

 

여기서 업데이트 횟수를 줄이기 위해서 소개할 방법은 2가지이다. 

첫 번째는 뷰를 하위 뷰로 나누는 것이다. 

만약 뷰를 하나의 뷰로 작성했다면 그에 필요한 데이터 값이 변경됨에 따라 뷰가 다시 그려지게 될 것이다. 

예를 들어 10개의 버튼이 있는데 각 버튼을 눌릴 때마다 각각의 버튼에 해당하는 뷰만 다시 그려지게 할 수 있지만 하나의 전체 뷰가 다시 그려지게 되면 버튼을 여러 번 누를 수록 성능의 차이가 많이 날 수 있다고 생각한다. 

 

var body: some View {
        HStack {
            Picker("MinutePicker", selection: $selectedMinute) {
                ForEach(0 ..< 59) {
                    Text("\($0)")
                }
            }
            Text("분")
                .font(Font(SDSFont.body1.font))
                .foregroundColor(Color(.gray600))
                .background(.random)
            Picker("SecondPicker", selection: $selectedSecond) {
                ForEach(0 ..< 59) {
                    Text("\($0)")
                }
            }
            Text("초")
                .font(Font(SDSFont.body1.font))
                .foregroundColor(Color(.gray600))
                .background(.random)
        }
    }

 

예시를 들기 위해 현재 개발 중인 앱에서 예시를 만들어 봤다. 

화면을 보면 타이머의 시간을 선택하기 위해서 picker를 조정하고 있는 모습이 보인다.
그리고 그에 따라 Text값이 다시 그려져 background의 색이 랜덤으로 계속 교체되고 있는 것을 알 수 있다. 

그럼 이를 개선하는 코드를 만들어 보자!

 

struct TimerTimeTextView: View {
    // MARK: - Property
    let text: String
    // MARK: - UI Property
    var body: some View {
        Text(text)
            .font(Font(SDSFont.body1.font))
            .foregroundColor(Color(.gray600))
            .background(.random)
    }
}

우선 둘의 차이는 텍스트 뿐이니 다음과 같이 컴포넌트를 만들어 주었다. 

var body: some View {
        HStack {
            Picker("MinutePicker", selection: $selectedMinute) {
                ForEach(0 ..< 59) {
                    Text("\($0)")
                }
            }
            TimerTimeTextView(text: "분")
            Picker("SecondPicker", selection: $selectedSecond) {
                ForEach(0 ..< 59) {
                    Text("\($0)")
                }
            }
            TimerTimeTextView(text: "초")
        }
    }

이번엔 방금과 다르게 타이머를 조정하는 과정에서 텍스트들의 백그라운드 색상이 변경되지 않고 있는 것을 알 수 있다. 

다음과 같이 하위 뷰들로 나눈 다면 불필요한 redraw작업을 최소화 할 수 있기 때문에 성능 향상을 이뤄낼 수 있을 것 같다. 

 

 

 

두 번째는 뷰가 꼭 필요한 종속성만을 갖게 하는 것이다. 

 

요즘 사용하고 있는 종속성 관리를 체크하는 방법은 다음과 같다.

                let _ = Self._printChanges()

 

다음과 같은 코드를 사용해서 어떤 것들이 변화가 있는지 확인할 수 있다.

물론 breakPoint를 사용해서 해보 수 있지만 이 코드를 스니펫으로 만들어 놓고 사용하니 훨씬 편리했다. 

 

여기서 주의할 점은
다음 코드가 계속 남아있으리라는 보장이 없기 때문에 이 코드를 붙여서 앱 스토어에 제출해서는 안된다. 

또한 디버깅에만 쓰일 메서드이기 때문에 런타임 성능에 영향을 미친다. 

 

먼저 ObserbedObject에 종속성을 가지고 있는 경우 타이머가 흐름에 따라 데이터가 변화되는 것을 알 수 있다.

뿐 만 아니라 현재  "일시정지"버튼 역시 ObservableObject에 프로퍼티 래퍼로 정의되어 있는데

그 버튼이 눌림에 따라 값이 변화함을 다시 감지하게 된다.

이는 불필요한 업데이트로 이어질 수 있다

 

 

반면 다음과 같이 필요한 데이터에만 종속성을 가질 경우 그에 상응하는 데이터가 아닌 데이터가 변경될 때는 뷰가 다시 그려지지 않는다. 
또한 WWDC에서는 다음과 같은 방법을 사용할 때 코드 읽기가 더 쉬워지고 종속성이 사용 위치에 드러난다는 장점이 있다고 한다.
하지만 모든 경우를 꼭 이렇게 해야한다는 것은 아니기 때문에 개발자의 판단에 맡겨야 한다고 한다. 

 

 

이렇게 성능 향상을 위한 방법들에 대해서 공부를 해보았는데 이런 사소한 습관들을 길러서 사이드 이펙트를 최소화하는 방법들을 계속 연구해 나가야겠다. 

 

https://developer.apple.com/videos/play/wwdc2023/10160/

 

Demystify SwiftUI performance - WWDC23 - Videos - Apple Developer

Learn how you can build a mental model for performance in SwiftUI and write faster, more efficient code. We'll share some of the common...

developer.apple.com

반응형

'SwiftUI' 카테고리의 다른 글

SwiftUI) GridItem 알아보기  (0) 2022.11.27
SwiftUI) LazyH(V)Grid 알아보기  (0) 2022.11.24
SwiftUI) Grid, GridRow 알아보기  (0) 2022.11.22
SwiftUI @Binding 알아보기  (0) 2022.10.23
SwiftUI FocusState 알아보기  (0) 2022.07.26