반응형

SwiftUI에서 많이 하는 실수 - ObservableObject에서 @Published를 남발하는 실수

 

ObservableObject에서 @Published는 값이 바뀔 때마다 View에 변경을 알리는 역할을 합니다.
모든 프로퍼티에 습관적으로 @Published를 붙이면, 작은 변경에도 불필요하게 많은 View 리렌더링이 발생합니다.


1. 문제 원인

  • “나중에 쓸 수도 있으니 일단 @Published 붙여두자”라는 습관
  • 어떤 값이 실제로 UI에 영향을 주는지 고민하지 않음
  • 하나의 ViewModel에 너무 많은 책임(도메인 + UI)을 몰아넣음

2. 나타나는 증상

  • 스크롤/입력 시 CPU 사용량이 높고 프레임 드랍
  • 작은 상태 변화에도 화면 전체가 깜빡이듯 재렌더링
  • 어떤 값이 변해도 특정 View가 계속 body를 다시 그리는 현상

3. 잘못된 코드 예시

final class CardsViewModel: ObservableObject {
    // ❌ 전부 @Published
    @Published var allCards: [Card] = []
    @Published var filteredCards: [Card] = []
    @Published var searchText: String = ""
    @Published var isLoading: Bool = false
    @Published var errorMessage: String? = nil
    @Published var selectedCard: Card? = nil
    @Published var lastUpdated: Date? = nil
}

위 상태에서 searchText만 바꿔도, 이 ViewModel을 구독하는 모든 View가 다시 계산됩니다.


4. 올바른 코드 예시

4-1. 진짜 UI에 필요한 값만 @Published

final class CardsViewModel: ObservableObject {
    // ✅ UI에 직접 그려지는 값 위주로
    @Published private(set) var filteredCards: [Card] = []
    @Published var searchText: String = ""
    @Published var isLoading: Bool = false
    @Published var errorMessage: String? = nil

    private var allCards: [Card] = []  // ✅ 내부 캐시는 @Published 불필요
    private var lastUpdated: Date? = nil

    func load() {
        // allCards, lastUpdated 변경은 내부에서만 사용
    }
}

4-2. 서브 ViewModel로 구조 분리

final class CardListState: ObservableObject {
    @Published var filteredCards: [Card] = []
    @Published var searchText: String = ""
}

final class CardsViewModel {
    let ui = CardListState()

    private var allCards: [Card] = []
    // 도메인 관련 값들...
}

UI는 CardListState만 구독하고,

나머지 도메인 값들은 별도의 계층에서 관리할 수도 있습니다.


5. 정리 및 팁

  • “이 값이 바뀌면 UI가 반드시 다시 그려져야 하는가?”를 기준으로 @Published 여부를 결정합니다.
  • 디버깅 시 body 호출 횟수를 로그로 찍어보면,
    어떤 Published가 과도하게 영향을 주는지 감을 잡을 수 있습니다.
struct SomeView: View {
    var body: some View {
        print("body:", Self.self)
        return Text("...")
    }
}
  • 너무 비대해진 ViewModel은 UI 상태 전용 객체, 도메인 서비스/UseCase 등으로 분해하는 것이 좋습니다.
반응형
Posted by 까칠코더
,