Demystify SwiftUI
SwiftUI엔 Identity, Lifetime, Dependency의 개념이 존재하는데, 해당 개념들은 SwiftUI가 뷰를 효율적으로 그리고 업데이트하는지를 이해하는데 매우 중요한 개념입니다.
- 정체성 (Identity): 뷰를 서로 구별하고 알아보는 방법
- 수명 (Lifetime): 뷰와 데이터가 언제까지 존재하는지를 추적하는 방법
- 종속성 (Dependencies): 뷰가 어떤 데이터에 의존하며, 언제 업데이트되어야 하는지를 파악하는 방법
Identity
Identity는 SwiftUI가 여러 화면 업데이트 과정에서 UI 요소를 "같은 것" 혹은 "다른 것"으로 인식하는 방법입니다. 아이콘이 사라졌다가 나타나는 fade 효과를 보일지, 아니면 slide 전환 효과를 보일지는 SwiftUI가 두 아이콘을 서로 다른 뷰로 보는 지, 같은 뷰의 다른 상태로 보는지에 따라 결정될 만큼 identity는 중요한 요소입니다.
Identity에는 2가지 종류가 있습니다.
Explicit Identity
개발자가 데이터나 커스텀 식별자를 통해 뷰에 직접 이름을 붙여주는 것으로, 마치 강아지에게 이름을 불러주는 것과 같습니다.
UIKit과 AppKit은 클래스로 구현되기 때문에 포인터를 통해 Explicity Identity를 구별하였습니다.
하지만 SwiftUI는 구조체로 이루어져 있기 때문에 값타입입니다. 뷰를 값타입으로 지정하게 된다면 포인터가 없고, 효율적으로 메모리를 관리할 수 있고, 작은 단위의 컴포넌트를 구현하는데 용이합니다.
대신 SwiftUI는 다른 방식으로 Explicit Identity를 구분합니다.
- ForEach: 리스트를 만들 때 ForEach(dogs, id: \.uuid)처럼 각 데이터에 고유한 ID를 부여합니다. 이렇게 하면 리스트의 순서가 바뀌거나 항목이 추가/삭제될 때 SwiftUI가 어떤 항목이 어떻게 변했는지 정확히 파악하여 부드러운 애니메이션을 보여줄 수 있습니다.
- id() 수식어: ScrollViewReader를 사용할 때 특정 뷰로 스크롤하기 위해 .id("header")처럼 뷰에 직접 ID를 부여할 수 있습니다.
하지만 모든 뷰를 Explicit Identity로 정의할 필요가 없는데, 그 이유는 SwiftUI는 뷰의 계층을 기반으로 Structural Identity를 생성해주기 때문입니다.
Structural Identity
개발자가 ID를 직접 주지 않아도, SwiftUI가 코드의 구조(타입과 위치)를 보고 암시적으로 정체성을 부여하는 방식입니다. 마치 "왼쪽에 앉은 강아지"처럼 위치로 구별하는 것과 같습니다.
if isLoggedIn {
AdoptionDirectory() // A
} else {
DogList() // B
}
하지만 구조를 통해 뷰를 구별하는 것은 SwiftUI가 해당 뷰들이 제자리에 머물고 위치를 바꾸지 않는다는 것을 정적으로(statically) 보장할 수 있을 때만 가능합니다. SwiftUI는 뷰 계층의 타입 구조를 확인하면서 이를 구별하는데, SwiftUI는 뷰의 ‘제네릭 타입’을 보고 있습니다.
if문은 내부적으로 _ConditionalContent 라는 뷰로 인식되어 참일때의 컨텐츠와 거짓일 때의 컨텐츠를 각각 다른 제네릭 타입으로 가집니다.
이러한 번역 과정은 @ViewBuilder에 의해 수행됩니다. View 프로토콜은 body 프로퍼티를 암시적으로 ViewBuilder로 감싸는데, 이 ViewBuilder가 body 안의 if문 같은 구문들을 모아 하나의 복합적인 제네릭 뷰로 만들어줍니다.
some View는 불투명 타입으로 복잡한 실제 타입 이름을 정의하지 않고도 사용할 수 있도록 도와줍니다. 이 제네릭을 통해 ‘참’인 분기는 항상 AdoptionDirectory이고, ‘거짓’인 분기는 항상 ‘DogList’라는 것을 보장할 수 있기 때문에 사용할 수 있습니다.
VStack {
if dog.isGood {
PawView(tint: .green)
Spacer()
} else {
Spacer()
PawView(tint: .red)
}
}
PawView(tint: dog.isGood ? .green : .red)
.frame(
maxHeight: .infinity,
alignment: dog.isGood ? .top : .bottom)
두 가지의 식이 있다고 할 때, if-else 문을 사용하면, 각 조건 분기는 서로 다른 뷰를 정의합니다. SwiftUI는 각 분기가 별도의 Identity를 가지고 있다고 이해하기 때문에, 상태가 바뀔 때 fade in-out 효과를 통해 화면이 전환됩니다.
하지만 하나의 View를 통해 구현한다면 Identity가 같기 때문에 slide 효과를 통해 화면이 움직입니다.
두 방법 모두 가능하지만, SwiftUI에선 일반적으론 두 번째 접근 방식을 권장하는데, 이는 기본적으로 뷰의 Identity를 유지하는 것이 lifetime과 state를 보존하는데 도움이 되기 때문입니다.
AnyView를 피해야 하는 이유
AnyView는 뷰의 구체적인 타입을 숨기는 '타입 지우개(type-erasing wrapper)'입니다. AnyView를 사용하면 SwiftUI는 뷰의 구조적 정보를 볼 수 없게 되어 정체성을 제대로 파악하기 어렵습니다. 이는 성능 저하를 유발하고 코드를 읽기 어렵게 만듭니다. 대신, 함수에 @ViewBuilder 속성을 붙여주면 AnyView 없이도 if나 switch 같은 조건문을 자유롭게 사용하여 SwiftUI가 뷰의 구조를 명확히 파악하게 할 수 있습니다.
'iOS > SwiftUI' 카테고리의 다른 글
[SwiftUI] WWDC2021 - Demystify SwiftUI - Lifetime (0) | 2025.09.07 |
---|---|
[SwiftUI] Source of Truth (0) | 2025.08.30 |
[SwiftUI] View Protocol과 ViewBuilder (0) | 2025.04.17 |
[SwiftUI] SwiftUI란 (0) | 2025.04.17 |
[SwiftUI] ViewModifier (0) | 2023.08.11 |