반응형

SwiftUI Study – AnyView 남용을 피하고 타입 안정성을 유지하는 방법

 

1. 왜 중요한가 (문제 배경)

SwiftUI는 제네릭 기반(type-safe) UI 프레임워크입니다.
이는 각 View의 타입이 컴파일 시점에 모두 결정되기 때문에 가능한 최적화가 매우 많다는 의미입니다.
그러나 이 타입 구조를 우회하는 대표적 도구가 바로 AnyView입니다.
AnyView를 무분별하게 사용하면 다음과 같은 문제가 발생합니다.

  • SwiftUI의 diffing 시스템이 정상적으로 동작하지 않아 성능 저하
  • Body 내부가 매번 새로운 View로 간주됨 → 불필요한 렌더링 증가
  • conditional View가 삐끗하면 애니메이션 튐 / 전환 오류 발생
  • 타입 정보가 사라져 컴파일러의 최적화가 차단
  • 유지보수 시 “이 View가 무엇인지” 파악하기 어려움

SwiftUI는 타입이 곧 View 구조이기 때문에,

타입을 지워버리는 AnyView는 정말 “마지막 수단”으로만 사용해야 합니다.

 

2. 잘못된 패턴 예시

❌ 예시 1: 조건문마다 AnyView로 감싸기

struct WrongConditionalView: View {
    @State private var isLoggedIn = false

    var body: some View {
        VStack {
            if isLoggedIn {
                AnyView(Text("Welcome!"))   // ❌ 불필요한 래핑
            } else {
                AnyView(Button("Login") { isLoggedIn = true })  // ❌
            }
        }
    }
}

문제점

  • SwiftUI는 내부 View 타입이 달라질 때 구분 처리를 자동으로 한다
  • AnyView로 감싸면 SwiftUI diffing 엔진이 “모든 변경을 완전 다른 View로 처리”
  • 결과적으로 렌더링/애니메이션이 비효율적이며 예측이 어려워짐

❌ 예시 2: ForEach 안에서 AnyView 사용

struct WrongListView: View {
    let items: [Item]

    var body: some View {
        ForEach(items) { item in
            AnyView(
                item.type == .text
                ? Text(item.title)
                : Image(systemName: item.icon)
            )
        }
    }
}

문제점

  • 리스트의 각 셀 타입이 숨겨져 SwiftUI가 identity/diff 계산을 정확히 하지 못함
  • 스크롤 시 셀 깜빡임 / 렌더링 반복 같은 문제가 자주 발생

❌ 예시 3: 뷰 전환 시 AnyView로 감싸기

struct WrongTransitionView: View {
    @State private var mode: Int = 0

    var body: some View {
        AnyView(
            mode == 0 ? Text("A") : Text("B")   // ❌ 트랜지션 애니메이션이 깨짐
        )
    }
}

문제점

  • SwiftUI는 타입을 기반으로 transition/animation 비교를 수행
  • AnyView로 감싸면 매번 완전히 새로운 View라고 판단하여 자연스러운 전환이 불가능함

 

3. 올바른 패턴 예시

✅ 예시 1: 조건부 View는 AnyView 대신 @ViewBuilder로 자연스럽게 처리

struct CorrectConditionalView: View {
    @State private var isLoggedIn = false

    var body: some View {
        VStack {
            if isLoggedIn {
                Text("Welcome!")
            } else {
                Button("Login") { isLoggedIn = true }
            }
        }
    }
}

장점

  • SwiftUI가 각 타입을 정확히 추적
  • 애니메이션, 전환 효과가 자연스럽게 적용됨
  • 성능 저하 없음

✅ 예시 2: 타입이 다른 View를 담아야 한다면 enum + computed property 패턴 사용

enum CellType {
    case text(String)
    case icon(String)

    @ViewBuilder
    var body: some View {
        switch self {
        case .text(let title):
            Text(title)
        case .icon(let name):
            Image(systemName: name)
        }
    }
}

struct CorrectListView: View {
    let items: [CellType]

    var body: some View {
        ForEach(Array(items.enumerated()), id: \.offset) { _, item in
            item.body
        }
    }
}

장점

  • 타입 안정성 유지
  • ViewBuilder를 통해 AnyView 없이 다양한 타입의 뷰 처리
  • 리스트 성능/렌더링 모두 안정적

✅ 예시 3: 정말로 필요할 때만 AnyView 사용

AnyView가 필요한 극히 제한적 상황

  • View 타입을 런타임에만 결정할 때
  • 매우 복잡한 제네릭 타입을 단순화해야 할 때
  • 특정 API가 “View 타입을 숨긴 Wrapper”를 요구할 때

예:

protocol CustomHost {
    func makeBody() -> AnyView
}

하지만 SwiftUI 자체에서는 거의 필요하지 않음.

 

4. 실전 적용 팁

✔ 팁 1 – “AnyView가 필요하다고 느껴지면 대부분 설계 신호”

대부분의 경우 다음 두 가지를 고민하면 해결됨:

  • 구조를 enum으로 재설계할 수 있는가?
  • @ViewBuilder로 분기 처리가 가능한가?

✔ 팁 2 – AnyView는 diffing 성능을 떨어뜨린다

SwiftUI는 View 타입 기반으로 “이전 View와 비교(diffing)” 를 수행하는데

AnyView는 타입 정보를 지워버려 최적화가 사라진다.

✔ 팁 3 – ViewBuilder는 다양한 타입을 자연스럽게 조합할 수 있음

조건문 / switch / loop에 완벽 대응

→ AnyView보다 SwiftUI 철학에 훨씬 적합

✔ 팁 4 – 리스트/그리드 셀에서는 특히 AnyView 금지

셀 단위 diffing 성능이 크게 떨어져

깜빡임/재렌더링 문제가 빈번하게 발생한다.

✔ 팁 5 – “타입이 곧 구조”라는 SwiftUI 기본 철학 이해하기

타입을 유지하는 것이 SwiftUI 성능과 예측가능성의 핵심이다.

 

5. 정리

  • AnyView는 SwiftUI의 타입 기반 최적화를 차단하는 “마지막 수단”이다.
  • 조건문/전환/리스트 등에서 AnyView를 남용하면 성능 저하와 예측 불가한 동작이 발생한다.
  • enum + @ViewBuilder 조합으로 대부분의 상황을 해결할 수 있다.
  • 실무에서는 AnyView를 거의 사용하지 않으며,
    SwiftUI의 타입 시스템을 최대한 존중하는 방식이 가장 안정적이다.
반응형
Posted by 까칠코더
,