SwiftUI: Why You Need AnyView
AnyView’s “performance problems”… and solutions.
medium.com
AnyView, 정말 나쁘기만 할까?
SwiftUI에서 AnyView
는 종종 피해야 할 기능처럼 언급됩니다. 하지만 그 이유를 제대로 설명할 수 있는 사람은 드물며, 많은 경우 단순히 "안 좋다"는 소문에 의존하고 있죠. 이번 포스트에서는 AnyView
에 대한 오해와 진실을 정리하고, 실제로 어떤 상황에서 유용하게 활용할 수 있는지를 풍부한 예시와 함께 소개합니다.
✨ 다양한 View 타입을 리턴하는 방법
SwiftUI는 some View
를 사용하는 구조이기 때문에, 조건에 따라 서로 다른 View 타입을 반환하는 것이 제한적입니다. 이러한 상황에서 사용할 수 있는 대표적인 방법은 다음과 같습니다:
ViewBuilder
를 활용한_ConditionalContent
타입 리턴Group
으로 View를 묶기- 타입 지우기(Type Erasure) →
AnyView
사용하기
이러한 방법들은 상황에 따라 선택되며, AnyView
는 특히 복잡한 조건 분기나 추상화를 필요로 할 때 유용합니다.
⚠️ AnyView = Destroying the Hierarchy?
Apple의 공식 문서와 WWDC 발표에서는 다음과 같은 주의를 줍니다:
"Whenever the type of view used with an AnyView changes, the old hierarchy is destroyed and a new hierarchy is created."
즉, AnyView
로 감싼 뷰의 타입이 바뀌면 SwiftUI는 뷰 트리를 파괴하고 새로 구성합니다. 하지만 이건 AnyView
에만 해당하는 특수한 상황이 아니라, SwiftUI의 일반적인 동작 원리입니다.
struct ConditionalView: View {
let condition: Bool
var body: some View {
if condition {
ViewA() // 타입 A
} else {
ViewB() // 타입 B
}
}
}
이 예제처럼 조건문에 따라 View의 타입이 바뀌면 SwiftUI는 해당 서브 트리를 새로 만들어야 합니다. AnyView
가 아니더라도 마찬가지입니다.
func view3(for item: Item) -> some View {
if item.id.isMultiple(of: 2) {
return AnyView(ItemView(item: item))
} else {
return AnyView(OtherView(item: item))
}
}
이 경우도 주의가 필요합니다. AnyView
로 감쌌지만 내부 타입이 다른 경우이므로 조건이 바뀔 때마다 트리가 새로 그려집니다. 하지만 내부 뷰 타입이 같다면, 트리 파괴 없이 상태를 유지한 채 동작할 수 있습니다.
🧨 View 타입 파괴가 진짜 문제인 이유
SwiftUI가 뷰 트리를 파괴하면 다음과 같은 부작용이 발생합니다:
@State
,@ObservedObject
,@EnvironmentObject
등 모든 상태 초기화onAppear
,onDisappear
재호출- 애니메이션 상태, 스크롤 위치 등도 리셋됨
즉, 단순한 modifier 차이더라도 View 타입이 바뀌는 순간, SwiftUI는 완전히 새로 그린다고 생각해야 합니다. UX 문제를 피하려면 View 타입의 일관성을 유지하는 것이 중요합니다.
📊 AnyView와 List의 관계
SwiftUI의 List
는 내부적으로 UICollectionView
로 구성되어 있으며, 최적화를 위해 다음 정보를 필요로 합니다:
- 아이템의 개수
- 각 아이템이 어떤 View를 사용하는지
struct NormalListView: View {
let items: [Item]
var body: some View {
List(items) { item in
ItemView(item: item)
}
}
}
이 경우 SwiftUI는 ItemView
가 동일한 타입이라는 걸 알기 때문에 화면에 필요한 만큼만 lazy하게 evaluate합니다.
struct AnyListView: View {
let items: [Item]
var body: some View {
List(items) { item in
AnyView(ItemView(item: item))
}
}
}
하지만 위와 같이 AnyView
로 타입이 지워지면, SwiftUI는 어떤 뷰가 나올지 모르기 때문에 모든 아이템을 evaluate하게 됩니다. 성능 이슈가 발생할 수 있죠.
💡 해결책
List(items) { item in
VStack { AnyView(ItemView(item: item)) }
}
이처럼 VStack
과 같은 구체적인 타입으로 감싸주면 SwiftUI는 타입 추론이 가능해져 다시 lazy 동작이 가능해집니다.
🔀 조건 분기와 switch도 마찬가지
List(items) { item in
if item.id.isMultiple(of: 2) {
ItemView(item: item)
} else {
OtherView(item: item)
}
}
이 경우도 ViewBuilder가 서로 다른 타입을 생성하므로, SwiftUI는 어떤 View가 나올지 알 수 없습니다. 결국 모든 아이템을 evaluate하게 되며, 성능에 불이익이 발생할 수 있습니다.
→ VStack
으로 감싸서 해결 가능합니다.
🔄 상태 변화와 AnyView의 관계
@State
변경 시 해당 셀만 렌더링됨 (expected behavior)- 부모 View가 업데이트 되어도, AnyView로 감싼 View의 타입이 유지된다면 상태는 유지됨
이로 인해 AnyView
는 상태 전파와 무관하게 안전하게 사용할 수 있습니다. 중요한 건 내부 View의 타입이 바뀌지 않는 것입니다.
🔧 프로토콜과 AnyView의 활용
protocol ItemViewProviding {
func view(for item: Item) -> AnyView
}
struct ProviderListView: View {
let items: [Item]
let provider: any ItemViewProviding
var body: some View {
List(items) { item in
VStack { provider.view(for: item) }
}
}
}
some View
는 protocol의 return type으로 쓸 수 없습니다. 그래서 AnyView
로 타입을 지워야 합니다. 이런 상황에서는 AnyView
가 유일하면서도 깔끔한 해결책입니다.
✅ 결론: AnyView는 그냥 View다
AnyView
는 타입을 감춘 View일 뿐, 특별한 동작을 하는 View가 아님- 잘못 사용하면 성능에 악영향을 줄 수 있지만, 올바르게 사용하면 매우 유연한 구조를 만들 수 있음
언제 사용해야 할까?
- 서로 다른 View를 조건에 따라 리턴할 때
- Protocol을 통해 View를 추상화할 때
- 타입 추론 문제로 컴파일 에러가 발생할 때
추가 팁
LazyVStack
,LazyHStack
등에서는AnyView
를 사용해도 lazy 로딩 동작이 잘 유지됩니다
🚀 마무리: 오해를 걷어내고, 도구로써 활용하자
AnyView
는 SwiftUI에서 제공하는 도구(tool)입니다. 오용하면 문제가 되지만, 필요할 때 정확히 사용하면 SwiftUI의 제약을 유연하게 풀어주는 열쇠가 됩니다.
무작정 피하지 말고, 언제 쓰는 게 적절한지를 이해하고 선택적으로 활용하세요. 그렇게 한다면 AnyView
는 여러분의 SwiftUI 개발을 더 깔끔하고 추상적인 구조로 만들어줄 수 있습니다.
'공부 > medium' 카테고리의 다른 글
[Swift] Swift 6.0 Protocol Extensions 추가 기능 (0) | 2025.04.01 |
---|---|
[Swift] Alamofire VS URLSession, Type Casting Performance (0) | 2025.03.26 |
SwiftUI: Why You Need AnyView
AnyView’s “performance problems”… and solutions.
medium.com
AnyView, 정말 나쁘기만 할까?
SwiftUI에서 AnyView
는 종종 피해야 할 기능처럼 언급됩니다. 하지만 그 이유를 제대로 설명할 수 있는 사람은 드물며, 많은 경우 단순히 "안 좋다"는 소문에 의존하고 있죠. 이번 포스트에서는 AnyView
에 대한 오해와 진실을 정리하고, 실제로 어떤 상황에서 유용하게 활용할 수 있는지를 풍부한 예시와 함께 소개합니다.
✨ 다양한 View 타입을 리턴하는 방법
SwiftUI는 some View
를 사용하는 구조이기 때문에, 조건에 따라 서로 다른 View 타입을 반환하는 것이 제한적입니다. 이러한 상황에서 사용할 수 있는 대표적인 방법은 다음과 같습니다:
ViewBuilder
를 활용한_ConditionalContent
타입 리턴Group
으로 View를 묶기- 타입 지우기(Type Erasure) →
AnyView
사용하기
이러한 방법들은 상황에 따라 선택되며, AnyView
는 특히 복잡한 조건 분기나 추상화를 필요로 할 때 유용합니다.
⚠️ AnyView = Destroying the Hierarchy?
Apple의 공식 문서와 WWDC 발표에서는 다음과 같은 주의를 줍니다:
"Whenever the type of view used with an AnyView changes, the old hierarchy is destroyed and a new hierarchy is created."
즉, AnyView
로 감싼 뷰의 타입이 바뀌면 SwiftUI는 뷰 트리를 파괴하고 새로 구성합니다. 하지만 이건 AnyView
에만 해당하는 특수한 상황이 아니라, SwiftUI의 일반적인 동작 원리입니다.
struct ConditionalView: View {
let condition: Bool
var body: some View {
if condition {
ViewA() // 타입 A
} else {
ViewB() // 타입 B
}
}
}
이 예제처럼 조건문에 따라 View의 타입이 바뀌면 SwiftUI는 해당 서브 트리를 새로 만들어야 합니다. AnyView
가 아니더라도 마찬가지입니다.
func view3(for item: Item) -> some View {
if item.id.isMultiple(of: 2) {
return AnyView(ItemView(item: item))
} else {
return AnyView(OtherView(item: item))
}
}
이 경우도 주의가 필요합니다. AnyView
로 감쌌지만 내부 타입이 다른 경우이므로 조건이 바뀔 때마다 트리가 새로 그려집니다. 하지만 내부 뷰 타입이 같다면, 트리 파괴 없이 상태를 유지한 채 동작할 수 있습니다.
🧨 View 타입 파괴가 진짜 문제인 이유
SwiftUI가 뷰 트리를 파괴하면 다음과 같은 부작용이 발생합니다:
@State
,@ObservedObject
,@EnvironmentObject
등 모든 상태 초기화onAppear
,onDisappear
재호출- 애니메이션 상태, 스크롤 위치 등도 리셋됨
즉, 단순한 modifier 차이더라도 View 타입이 바뀌는 순간, SwiftUI는 완전히 새로 그린다고 생각해야 합니다. UX 문제를 피하려면 View 타입의 일관성을 유지하는 것이 중요합니다.
📊 AnyView와 List의 관계
SwiftUI의 List
는 내부적으로 UICollectionView
로 구성되어 있으며, 최적화를 위해 다음 정보를 필요로 합니다:
- 아이템의 개수
- 각 아이템이 어떤 View를 사용하는지
struct NormalListView: View {
let items: [Item]
var body: some View {
List(items) { item in
ItemView(item: item)
}
}
}
이 경우 SwiftUI는 ItemView
가 동일한 타입이라는 걸 알기 때문에 화면에 필요한 만큼만 lazy하게 evaluate합니다.
struct AnyListView: View {
let items: [Item]
var body: some View {
List(items) { item in
AnyView(ItemView(item: item))
}
}
}
하지만 위와 같이 AnyView
로 타입이 지워지면, SwiftUI는 어떤 뷰가 나올지 모르기 때문에 모든 아이템을 evaluate하게 됩니다. 성능 이슈가 발생할 수 있죠.
💡 해결책
List(items) { item in
VStack { AnyView(ItemView(item: item)) }
}
이처럼 VStack
과 같은 구체적인 타입으로 감싸주면 SwiftUI는 타입 추론이 가능해져 다시 lazy 동작이 가능해집니다.
🔀 조건 분기와 switch도 마찬가지
List(items) { item in
if item.id.isMultiple(of: 2) {
ItemView(item: item)
} else {
OtherView(item: item)
}
}
이 경우도 ViewBuilder가 서로 다른 타입을 생성하므로, SwiftUI는 어떤 View가 나올지 알 수 없습니다. 결국 모든 아이템을 evaluate하게 되며, 성능에 불이익이 발생할 수 있습니다.
→ VStack
으로 감싸서 해결 가능합니다.
🔄 상태 변화와 AnyView의 관계
@State
변경 시 해당 셀만 렌더링됨 (expected behavior)- 부모 View가 업데이트 되어도, AnyView로 감싼 View의 타입이 유지된다면 상태는 유지됨
이로 인해 AnyView
는 상태 전파와 무관하게 안전하게 사용할 수 있습니다. 중요한 건 내부 View의 타입이 바뀌지 않는 것입니다.
🔧 프로토콜과 AnyView의 활용
protocol ItemViewProviding {
func view(for item: Item) -> AnyView
}
struct ProviderListView: View {
let items: [Item]
let provider: any ItemViewProviding
var body: some View {
List(items) { item in
VStack { provider.view(for: item) }
}
}
}
some View
는 protocol의 return type으로 쓸 수 없습니다. 그래서 AnyView
로 타입을 지워야 합니다. 이런 상황에서는 AnyView
가 유일하면서도 깔끔한 해결책입니다.
✅ 결론: AnyView는 그냥 View다
AnyView
는 타입을 감춘 View일 뿐, 특별한 동작을 하는 View가 아님- 잘못 사용하면 성능에 악영향을 줄 수 있지만, 올바르게 사용하면 매우 유연한 구조를 만들 수 있음
언제 사용해야 할까?
- 서로 다른 View를 조건에 따라 리턴할 때
- Protocol을 통해 View를 추상화할 때
- 타입 추론 문제로 컴파일 에러가 발생할 때
추가 팁
LazyVStack
,LazyHStack
등에서는AnyView
를 사용해도 lazy 로딩 동작이 잘 유지됩니다
🚀 마무리: 오해를 걷어내고, 도구로써 활용하자
AnyView
는 SwiftUI에서 제공하는 도구(tool)입니다. 오용하면 문제가 되지만, 필요할 때 정확히 사용하면 SwiftUI의 제약을 유연하게 풀어주는 열쇠가 됩니다.
무작정 피하지 말고, 언제 쓰는 게 적절한지를 이해하고 선택적으로 활용하세요. 그렇게 한다면 AnyView
는 여러분의 SwiftUI 개발을 더 깔끔하고 추상적인 구조로 만들어줄 수 있습니다.
'공부 > medium' 카테고리의 다른 글
[Swift] Swift 6.0 Protocol Extensions 추가 기능 (0) | 2025.04.01 |
---|---|
[Swift] Alamofire VS URLSession, Type Casting Performance (0) | 2025.03.26 |