View Protocol은 모든 시각적 인터페이스 구성 요소가 준수해야 하는 SwiftUI의 기본 프로토콜입니다. Apple Developer Documentation, 2024에 따르면, View는 단일 계약을 정의합니다. 이 프로토콜을 구현하는 구조체나 클래스는 계산된 속성 body를 제공해야 합니다. 이 프로토콜을 통해 SwiftUI는 단순한 텍스트 레이블부터 복잡한 탐색 구조까지 전체 화면 계층을 구축합니다.
핵심 포인트
View Protocol은 모든 시각적 요소가 콘텐츠를 설명하는 방식을 정의하는 SwiftUI의 핵심 프로토콜입니다. 각 요소가 클래스를 통해 UIView에서 상속받는 UIKit과 달리, SwiftUI는 프로토콜 지향 접근 방식을 사용합니다. View 프로토콜을 준수하는 모든 유형은 화면에 표시될 수 있습니다.
View 프로토콜은 콘텐츠를 반환하는 단일 계산된 속성 body의 구현을 요구합니다. 그러나 이 단순함 뒤에는 강력한 구성 시스템이 숨어 있습니다. body는 기본 요소(Text, Image, Button), 컨테이너(VStack, HStack, ZStack) 및 사용자 정의 복합 구성 요소를 포함하여 View를 준수하는 모든 유형을 반환할 수 있습니다.
WWDC 2023에 따르면, SwiftUI 애플리케이션의 전체 화면 중 95% 이상이 View 프로토콜을 구현하는 구조체의 구성을 통해 구축됩니다. 이는 View Protocol을 SwiftUI 아키텍처 전체의 기초로 만듭니다.
SwiftUI는 View가 값 유형(value type)(struct)이어야 하며 클래스가 아니어야 합니다. 이는 중요한 아키텍처 결정입니다. 값 유형은 예측 가능한 수명을 가지며, 공유 변경 가능 상태가 없고, SwiftUI가 계층 구조의 어떤 부분이 변경되어 다시 그려야 하는지 효율적으로 결정할 수 있게 합니다.
View를 클래스로 만들려고 하면 컴파일러가 오류를 발생시킵니다. View 프로토콜은 값 의미론(value semantics)이 필요한 DynamicViewProperty 프로토콜에서 상속됩니다. 클래스도 View를 준수할 수 있지만, 이는 관용적 접근 방식을 위반하고 자동 업데이트의 이점을 잃게 됩니다.
body는 View 프로토콜의 유일한 필수 요구 사항입니다. 화면에 표시될 콘텐츠를 반환하는 계산된 속성입니다. 반환 유형은 some View로, 이는 “View를 준수하는 유형으로, 컴파일러에 의해 결정됨”을 의미합니다.
struct GreetingView: View {
var name: String
var body: some View {
VStack {
Text("Hello, \(name)!")
.font(.title)
.foregroundColor(.blue)
Button("Start") {
print("Button pressed")
}
}
}
}
body 작동 방식: SwiftUI는 애플리케이션 상태가 변경되고 다시 그리기가 필요할 때마다 body를 호출합니다. 프레임워크는 새 View 트리를 이전 트리와 비교하고 필요한 변경 사항만 적용합니다(diffing). 이는 완전히 선언적인 접근 방식입니다. 무엇을 표시해야 하는지 설명하면 SwiftUI가 구현 방법을 처리합니다.
중요한 세부 사항: body에는 부작용이 없어야 합니다. 애플리케이션 수명 동안 여러 번 호출되며, body가 외부 상태를 수정하면 예측 불가능한 동작이 발생합니다. 부작용을 위해서는 task, onChange 또는 DispatchQueue를 사용하세요.
SwiftUI는 제한을 둡니다. body는 단일 루트 요소만 반환할 수 있습니다. 같은 수준에서 여러 요소를 표시해야 하는 경우 컨테이너(VStack, HStack, ZStack 또는 Group)로 감싸세요. @ViewBuilder의 도입으로 이 제한은 덜 눈에 띄게 되었지만, 개념적으로 body는 항상 단일 View를 반환합니다.
some View는 Swift 5.1에서 SwiftUI를 위해 특별히 도입된 불투명 유형(opaque type) 구문입니다. 함수나 속성이 View 프로토콜을 준수하는 구체적인 유형을 반환하지만, 호출 코드는 반환되는 정확한 유형을 알 필요가 없음을 의미합니다.
Swift 컴파일러는 각 body 구현에 대해 컴파일 타임에 구체적인 유형을 고정하지만, 외부 세계로부터 숨깁니다. 이를 통해 SwiftUI는 모든 구성 요소의 정확한 유형을 알면서 View 계층을 최적화할 수 있으며, 동시에 개발자는 시그니처를 변경하지 않고 구현을 변경할 수 있는 유연성을 얻습니다.
struct ContentView: View {
var body: some View {
Text("Hello, World!") // Compiler knows this is Text
}
}
왜 some View이고 단순히 View가 아닌가? body가 단순히 View(프로토콜로서)를 반환한다면, SwiftUI는 컴파일 타임에 구체적인 유형을 결정할 수 없습니다. 이는 실존 컨테이너(existential container)에 래핑하는 오버헤드를 추가합니다. some View는 프로토콜의 유연성을 유지하면서 컴파일러에 최적화를 위한 충분한 정보를 제공합니다.
주요 제한 사항은 body가 동일한 유형을 반환해야 한다는 것입니다. 특별한 래퍼(AnyView, Group 또는 @ViewBuilder) 없이 조건문의 한 분기에서 Text를 반환하고 다른 분기에서 Image를 반환할 수 없습니다. 컴파일러는 컴파일 타임에 이를 확인합니다. 모든 가능한 반환 경로는 동일한 유형이어야 합니다.
이 제한을 우회하기 위해 @ViewBuilder(단일 TupleView 유형 생성), Group(단일 유형 반환) 또는 AnyView(유형을 지우지만 오버헤드 추가)를 사용합니다. AnyView는 다른 옵션이 불가능할 때만 사용해야 하며, SwiftUI 최적화를 비활성화합니다.
@ViewBuilder는 중첩된 컨테이너 없이 여러 View를 하나의 구성으로 조합할 수 있게 하는 result builder입니다. @ViewBuilder는 자동으로 여러 표현식을 튜플(TupleView)로 감싸거나 조건부 로직(If / else / switch)을 올바른 반환 유형과 함께 적용합니다.
struct DashboardView: View {
var isLoggedIn: Bool
@ViewBuilder
var body: some View {
if isLoggedIn {
Text("Welcome!")
.font(.largeTitle)
ProfileCard()
} else {
LoginButton()
.padding()
}
}
}
@ViewBuilder 작동 방식: 컴파일러는 @ViewBuilder 내부의 각 코드 블록을 buildBlock, buildEither, buildOptional 등의 정적 메서드 호출로 변환합니다. 블록에 여러 표현식이 포함된 경우 TupleView로 감싸집니다. 블록에 조건부 로직이 포함된 경우 컴파일러는 ConditionalContent를 생성하여 분기 유형을 숨깁니다.
@ViewBuilder는 제한을 둡니다. 블록당 최대 10개 요소(TupleView 제한)입니다. 10개 이상의 요소를 조합해야 하는 경우 Group, ForEach를 사용하거나 하위 구성 요소로 분할하세요. 이 제한은 Swift가 1부터 10까지 각 인수 개수에 대해 별도의 buildBlock 오버로드를 생성하기 때문에 존재합니다.
구성(Composition)은 SwiftUI의 핵심 원칙입니다. 복잡한 인터페이스는 작고 재사용 가능한 View 구성 요소로 구축됩니다. 각 구성 요소는 View 프로토콜을 구현하고 화면의 자신의 부분을 담당합니다. 수정자(font, padding, foregroundColor)는 View에 적용되고 변경된 설정으로 새 View를 반환합니다.
SwiftUI의 수정자는 변경(mutation)이 아니라 원래 View 주위에 새 래퍼를 만드는 것입니다. 각 수정자는 새 유형(ModifiedContent)을 반환하여 SwiftUI가 수정자 트리를 구축하고 변경된 부분만 효율적으로 다시 그릴 수 있게 합니다. 수정자 적용 순서가 중요합니다. 다른 순서는 다른 시각적 결과를 만듭니다.
Text("Hello, SwiftUI!")
.font(.title) // ModifiedContent
.padding() // ModifiedContent<..., PaddingModifier>
.background(.yellow) // ModifiedContent<..., BackgroundModifier>
.cornerRadius(8) // ModifiedContent<..., CornerRadiusModifier>
성능 최적화: SwiftUI는 View의 구체적인 값을 비교하지 않고 ID 메커니즘(id, ForEach, 구조체의 안정적인 정체성)을 통해 정체성을 비교합니다. View 구조가 변경되지 않은 경우 body는 호출되지 않습니다. 이는 Equatable 비교와 계층 위로 데이터를 전달하기 위한 PreferenceKey 메커니즘을 통해 달성됩니다.
효과적인 구성을 위해 복잡한 화면을 독립적인 하위 구성 요소로 분할하는 것이 좋습니다. 각 구성 요소는 자체 최소 상태를 가집니다. 이를 통해 SwiftUI는 전체 화면이 아닌 계층의 변경된 부분만 다시 그릴 수 있습니다.
자주 묻는 질문
View Protocol은 표시되는 모든 구성 요소가 준수해야 하는 SwiftUI의 기본 프로토콜입니다. 콘텐츠를 반환하는 단일 계산된 body 속성이 필요합니다. Text, Button, Image, VStack과 같은 모든 표준 SwiftUI 요소는 이 프로토콜을 구현합니다.
SwiftUI는 예측 가능한 인터페이스 업데이트를 위해 값 의미론(value semantics)을 사용합니다. 구조체는 공유 변경 가능 상태가 없어 SwiftUI가 이전 View 계층과 새 View 계층을 효율적으로 비교하고 변경된 요소만 다시 그릴 수 있습니다. 클래스는 이 최적화를 깨뜨립니다.
body는 some View를 반환합니다. 이는 구체적인 구현을 숨기는 불투명 유형입니다. 실제로는 Text, Image, VStack, 사용자 정의 구조체 등 View를 준수하는 모든 유형을 반환합니다. 컴파일러는 최적화를 위해 컴파일 타임에 구체적인 유형을 고정합니다.
some View는 컴파일 타임에 구체적인 유형이 고정된 불투명 유형입니다. AnyView는 유형 소거(type erasure)로, 모든 View를 단일 컨테이너에 래핑합니다. some View가 더 효율적이며, AnyView는 오버헤드를 추가하므로 동적 유형 전환이 필요할 때만 사용됩니다.
최대 10개 요소입니다. 이는 TupleView 제한으로, 1부터 10까지의 인수 개수에 대해 buildBlock을 생성합니다. 더 많은 요소가 필요한 경우 Group, ForEach, List를 사용하거나 하위 구성 요소로 분할하세요. 이 제한은 Swift 컴파일러 수준에 존재합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.