서론

SwiftUI는 함수형 프로그래밍으로 직관적이고 가독성 높게 UI 로직을 작성할 수 있습니다.
특히 View의 구조를 바깥에서 안쪽으로 쉽게 파악할 수 있어 디버깅이 쉬우며
순수 함수로 로직을 구성하여 body 외부에서 발생할 수 있는 부작용을 최소화할 수 있습니다.

그러나 View 로직을 구성할 때 @State, @Binding 과 같은 값의 변화를 지속적으로 관찰하고,
그에 따라 UI를 다시 계산하고 렌더링해야 합니다.
이 과정에서 쉽게 발생할 수 있는 개발자의 실수로 인해 다시 렌더링할 필요가 없는 View까지 렌더링되는 문제가 자주 발생합니다.


SwiftUI에서 렌더링

SwiftUI의 View에서 사용하는 property wrapper들은 모두 DynamicProperty 프로토콜을 채택하고 있습니다.
해당 wrapper로 감싸진 property는 View의 body를 계산하기 위한 값들을 저장하고 있습니다.

SwiftUI는 위의 property 값을 관찰하고 있습니다.
만약 property 값이 변하게 되면 SwiftUI가 렌더링이 일어나기 전(body를 다시 그리기 전에) update() 함수를 호출하게 되고, 이에 따라 View가 다시 그려지면서 렌더링이 일어나게 됩니다.


렌더링을 확인하는 방법

SwiftUI에서 View가 렌더링 되는 것을 확인하려면 View에 print문을 사용하면 됩니다.
이 때, SwiftUI View 제공해주는 기능인 Self._printChanges() 를 활용하면 더욱 좋습니다.
이 함수는 View가 어떤 값의 변화에 의해 다시 렌더링 되는지를 확인시켜 줍니다.

import SwiftUI

struct ContentView: View {
    @State private var value: Int = 0

    var body: some View {
        let _ = Self._printChanges()
        Button {
            value += 1
        } label: {
            Text("Value = \(value)")
        }
    }
}


불필요한 렌더링 예시

ContentView에 VStack을 이용해 두 개의 View를 두었습니다.
targetView는 value: Int 와 무관한 View이고, Button은 value가 변함에 따라 label을 업데이트 해야 합니다.
이럴 경우 단순한 방법으로 View 로직을 구성하게 되면 value: Int 값 변화에 targetView도 다시 렌더링이 일어날 수 있습니다.

import SwiftUI

struct ContentView: View {
    @State private var value: Int = 0

    var body: some View {
        VStack {
            targetView
            Button {
                value += 1
            } label: {
                Text("Value = \(value)")
            }
        }
    }
    
    @ViewBuilder
    var targetView: some View {
        let _ = Self._printChanges()
        Text("렌더링 되고 싶지 않아요")
    }
}


해결 방법 : 별개의 View로 모듈화 하기

targetView를 별도의 struct로 분리하여 모듈화를 합니다.
이렇게 되면 value: Int와 완벽하게 분리가 되어 Text가 다른 View에 존재하게 됩니다.
불필요한 렌더링이 다시 일어나지 않도록 문제를 해결할 수 있습니다!

import SwiftUI

struct ContentView: View {
    @State private var value: Int = 0

    var body: some View {
        VStack {
            TargetView()
            Button {
                value += 1
            } label: {
                Text("Value = \(value)")
            }
        }
    }
}

struct TargetView: View {
    var body: some View {
        let _ = Self._printChanges()
        Text("렌더링 되고 싶지 않아요")
    }
}


결론

SwiftUI의 View를 사용할 때는 @State 또는 @Binding 값의 변화에 View가 다시 그려질 수 있음을 항상 생각해야 합니다.
이 때문에 @State 값에 관계가 없어 보이는 View의 경우에는 모듈화를 잘 시켜 주는 것이 좋습니다.
하지만, 이렇게 되면 너무 많은 재사용 되지 않을 View 파일이 생길 수 있습니다.
그러므로 재사용이 되지 않는다면 fileprivate와 같은 접근 제어자를 적절히 사용하여 같은 파일에 모듈화된 여러개의 View를 두는것이 좋아 보입니다.

다음 포스트 : https://velog.io/@sustainable-git/SwiftUI에서-불필요한-렌더링-제거하기2

profile
https://github.com/sustainable-git

0개의 댓글

Powered by GraphCDN, the GraphQL CDN