반응형
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의 타입 시스템을 최대한 존중하는 방식이 가장 안정적이다.
반응형
'Dev Study > SwiftUI' 카테고리의 다른 글
| SwiftUI Study – NavigationStack을 안정적으로 설계하는 법 (중복 Push·잘못된 경로·상태 꼬임 방지) (0) | 2025.12.09 |
|---|---|
| SwiftUI Study – 이미지 로딩·캐싱·리사이징을 효율적으로 처리하는 실전 패턴 (0) | 2025.12.09 |
| SwiftUI Study – ScrollView 안에서 LazyVStack을 사용해 성능을 최적화하는 방법 (0) | 2025.12.09 |
| SwiftUI Study – body 안에서 무거운 연산을 실행하지 않는 이유와 해결법 (0) | 2025.12.09 |
| SwiftUI Study – View 중첩이 깊어지지 않도록 컴포넌트를 분리하는 실전 패턴 (0) | 2025.12.09 |
| SwiftUI Study – offset / position으로 레이아웃을 잡지 말아야 하는 이유 (0) | 2025.12.09 |
| SwiftUI Study – GeometryReader에 의존하지 않고 레이아웃을 안정적으로 구성하는 방법 (0) | 2025.12.09 |
| SwiftUI Study – 상태 변경을 한 곳으로 모으는 패턴 (0) | 2025.12.09 |

