클라이언트

[SwiftUI] SwiftUI 성능 최적화: View Rendering을 줄이는 방법

애기공룡훈련병 2025. 2. 15. 16:46
반응형

SwiftUI는 선언형 UI 프레임워크로, 자동으로 UI를 업데이트해주는 장점이 있지만,

성능을 고려하지 않으면 불필요한 Re-Rendering이 발생할 수 있습니다.

이번 글에서는 SwiftUI의 성능을 최적화하는 5가지 방법을 소개합니다.

1. @State의 과도한 사용 줄이기

잘못된 예제

@State가 변경될 때마다 전체 body가 다시 평가되므로 성능 저하가 발생할 수 있습니다.

struct CounterView: View {
    @State private var count = 0
    
    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

문제점: count가 변경될 때마다 body가 전체적으로 다시 평가됩니다.

개선 방법

뷰를 별도의 struct로 분리하면, count 변경 시 최소한의 렌더링만 수행됩니다.

struct ParentView: View {
    @State private var count = 0
    
    var body: some View {
        VStack {
            Button("Increment") {
                count += 1
            }
            CounterLabel(count: count)
        }
    }
}

struct CounterLabel: View {
    let count: Int
    var body: some View {
        Text("Count: \(count)")
    }
}

개선점: CounterLabel이 독립적인 뷰로 분리되어 불필요한 재평가를 방지할 수 있습니다.

2. EquatableView 활용하기

SwiftUI는 기본적으로 값이 변경될 때마다 body를 다시 호출하지만, EquatableView를 사용하면 값이 동일할 경우 재평가를 방지할 수 있습니다.

예제 코드

struct CustomText: View, Equatable {
    var text: String
    
    var body: some View {
        Text(text)
    }
    
    static func == (lhs: CustomText, rhs: CustomText) -> Bool {
        return lhs.text == rhs.text
    }
}

효과: 값이 변경되지 않는 한 렌더링이 다시 일어나지 않습니다.

3. @ViewBuilder를 활용한 뷰 분리

뷰 내부에서 if, switch 문을 사용할 경우, SwiftUI는 매번 body를 평가하면서 전체 UI를 다시 구성할 수 있습니다.

잘못된 예제

struct ContentView: View {
    @State private var isOn = false
    
    var body: some View {
        VStack {
            Toggle("Switch", isOn: $isOn)
            if isOn {
                Text("On 상태입니다")
            } else {
                Text("Off 상태입니다")
            }
        }
    }
}

문제점: if문 내부의 모든 뷰가 매번 재평가됩니다.

개선 방법

struct ContentView: View {
    @State private var isOn = false
    
    var body: some View {
        VStack {
            Toggle("Switch", isOn: $isOn)
            DisplayText(isOn: isOn)
        }
    }
}

struct DisplayText: View {
    let isOn: Bool
    var body: some View {
        Text(isOn ? "On 상태입니다" : "Off 상태입니다")
    }
}

개선점: DisplayText를 별도 뷰로 분리하여 불필요한 렌더링을 방지할 수 있습니다.

4. LazyVStackLazyHStack 사용하기

VStackHStack은 모든 자식 뷰를 한 번에 렌더링하지만, LazyVStackLazyHStack은 필요한 부분만 렌더링하여 성능을 향상시킵니다.

예제 코드

struct ListView: View {
    let items = Array(1...1000)
    
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(items, id: \ .self) { item in
                    Text("Item \(item)")
                }
            }
        }
    }
}

📌 효과: 필요한 항목만 로드하여 성능을 최적화할 수 있습니다.

5. PreferenceKey를 활용하여 부모-자식 간 데이터 전달 최소화

@Binding이나 @EnvironmentObject를 사용할 경우 전체 뷰가 다시 렌더링될 수 있습니다. 이를 방지하려면 PreferenceKey를 사용하여 데이터 전달을 최소화할 수 있습니다.

예제 코드

struct ContentView: View {
    @State private var title = "Default Title"
    
    var body: some View {
        VStack {
            Text(title)
                .onPreferenceChange(TitlePreferenceKey.self) { newValue in
                    title = newValue
                }
            ChildView()
        }
    }
}

struct ChildView: View {
    var body: some View {
        Text("Tap Me")
            .preference(key: TitlePreferenceKey.self, value: "Updated Title")
    }
}

struct TitlePreferenceKey: PreferenceKey {
    static var defaultValue: String = ""
    static func reduce(value: inout String, nextValue: () -> String) {
        value = nextValue()
    }
}

효과: 부모 뷰 전체가 다시 렌더링되지 않고, 필요한 값만 갱신됩니다.

 

결론

SwiftUI의 성능을 최적화하려면 불필요한 뷰 렌더링을 최소화하는 것이 중요합니다.

반응형