Xcode를 통해 SwiftUI 프로젝트를 생성한 이후,
첫 화면인 ContentView에 담긴 코드를 유심히 살펴보다 보면..
위와 같이 구조체(Struct), 그리고 View 프로토콜을 채택하고 있다는건 얼추 이해가 가는데..
뷰를 그리는 body 메서드는
some View 라는 요상하게 생긴 타입을 반환하고 있습니다!?
궁금한 점은 어물쩡 넘어갈 수 없으니,
해당 키워드가 무슨 역할을 맡고 있고, 왜 작성되어야 하는지 살펴보는 포스팅 시간을 가져볼까 합니다.
불투명한 반환 타입(Opaque Return Type)
제너릭 타입과는 반대로, 함수의 '내부'에서 타입을 결정
사실, 프로토콜과 함께 다뤄야 하는 문법적 요소이나
불투명 반환타입을 살펴보기 위해 'someView'는 매우 좋은 예시가 되며,
해당 타입이 SwiftUI API의 기본 토대가 되므로, 이번 파트에서 짚고 넘어가도록 하겠습니다.
제너릭(Generic) 다시보기
- 제너릭의 경우, 함수를 호출하거나 인스턴스를 생성할 시 '타입'이 결정됩니다.
- 함수나 클래스, 구조체, 열거형의 외부에서 해당 매개변수나 메서드를 호출할 때 타입이 판단됩니다.
- 다시 말해, 함수 구현 내부에서는 사용자가 입력하는 값의 실제 타입을 알 수 없습니다!
- 아래 isEqual 함수는 제네릭으로 선언되어 있으며, a와 b 매개변수의 일치여부를 판단합니다.
- 앞선 제네릭 포스팅에서 살펴본 바와 같이, a와 b는 Equatbale 프로토콜을 준수합니다.
- 여기서, 제네릭 타입 T는 함수 '외부'에서 타입이 결정됩니다.
// where 절을 활용해서 프로토콜을 채택할 수 있다!
func isEqual<T>(a: inout T, b: inout T) -> Bool where T: Equatable {
return a == b
}
// 위 코드에서는, isEqual이란 함수 내부의 a, b의 매개변수가 어떤 타입인지 불분명함!
// 이를 결정하는 것이, 바로 함수를 호출하는 '외부' 였음
var intA: Int = 5
var intB: Int = 10
isEqual(a: &intA, b: &intB) // 잘 작동하는 동시에, T는 Int란 타입으로 결정됨
불투명한 타입(Opaque Type)
- 불투명한 타입(Opaque Type)은 흔히 '역 제너릭 타입'으로 불립니다.
- 즉, 제너릭 타입의 역할이 반전되는 것을 바로 불투명한 타입으로 볼 수 있습니다.
- 아래와 같이 한 가지 예시를 통해 불투명한 타입에 대해 살펴보도록 하겠습니다.
- 2가지 유형의 상품을 판매하는 선물가게가 있습니다.
- IPad와 Pensil을 준비하였으며, 'GiftBox'란 박스(프로토콜)을 생성합니다.
- GiftBox엔 gift란 프로퍼티가 선언되어 있으며, 프로토콜의 제네릭 타입인 <giftType>으로 선언되어 있습니다.
// 2개의 상품
struct IPad { }
struct Pensil { }
// GiftBox란 프로토콜을 만들고, 내부에 giftType란 제네릭 타입을 선언
protocol GiftBox {
associatedtype giftType // assoaciatedtype이란, 프로토콜 내부의 제네릭 타입
// 연산 프로퍼티 gift는 내부 giftType 제네릭 타입이며, getter 속성만 가지고 있음
var gift: giftType { get } // 연산 프로퍼티
}
- 이후, 준비된 GiftBox란 박스(프로토콜)에 맞춰 2개의 상품을 담아냅니다.
- 'IPadBox'와 'PensilBox'란 구조체는 GiftBox 프로토콜을 채택합니다.
- 내부에 있는 연산 프로퍼티인 gift는 기존에 선언해 둔 Ipad, Pensil 구조체 타입을 가져갑니다.
struct IPadBox: GiftBox {
var gift: IPad
}
struct PensilBox: GiftBox {
var gift: Pensil
}
- 마무리로 GiftBox의 내용물이 보이지 않도록, 잘 포장하는 기능의 함수를 만듭니다.
- 포장하는 행위는 동일하기 때문에, 반환하는 타입은 GiftBox(프로토콜)로 선언합니다.
- 오늘은 Pensil(타입)만 포장하고자 했는데, 갑자기 오류가 발생합니다?
- 이유는 즉, 해당 함수의 반환타입(GiftType)과 반환값이 일치하지 않기 때문입니다!
- GiftBox 프로토콜의 제네릭 타입인 GiftType은 함수 외부에서 결정되어야 합니다.
- 그런데, 위 코드의 경우 함수 내부에서 이미 반환타입(PensilBox)을 결정하고 있지요?
func makeBox() -> GiftBox {
// GiftBox에 선언된 제네릭 타입(giftType)과 반환타입이 일치하지 않는다는 내용
return PensilBox.init(gift: .init())
}
// 박스 외부에서는 해당 박스에 무슨 타입(제품)이 들어가 있는지 알 수 없다!
- 따라서, GiftType을 함수 내부에서 결정시켜 보내주기 위한 '선언 키워드' 가 필요합니다.
- makeBox()의 반환타입인 GiftBox 앞에 some 키워드를 선언합니다.
- 결과적으로, GiftBox 프로토콜은 '불투명 타입'이 되며, 함수 내부에서 타입을 결정합니다.
Some View의 역할
불 투명 반환타입을 활용, 화면을 그리는 body 프로퍼티의 타입 명명을 심플하게
앞서 불 투명 반환타입의 특징을 정리하자면 다음과 같습니다.
- 제네릭 타입과는 반대로, '역 제네릭 타입' 특성을 가지고 있음
- some 키워드 선언을 통해 함수 내부에서 반환타입을 결정
앞선 파트에서 매우 장황하게 설명했는데..
단 두 줄로도 정리될 만큼 생각보다 간단한 내용이었습니다.
그렇다면, SwiftUI에서 some View는 어떤 역할을 담당하고 있을까요?
body와 반환타입
- SwiftUI를 생성하게 되면, 아래와 같은 ContentView를 살펴볼 수 있습니다!
- body는 ContentView가 ➟ View라는 프로토콜을 채택함에 있어서 정의된 프로퍼티 입니다.
- 또한, 연산 프로퍼티이므로 괄호 { } 내부에 Text를 반환하고 있는 것으로 보입니다.
struct ContentView: View {
var body: some View {
Text("Hello, 불투명 반환타입")
}
}
- 여기서 'some'이란 키워드를 통한 불투명 반환타입으로의 전환은, 매우 큰 이점을 가져다 줍니다.
- 아래 코드에서의 View의 타입은, 사실 VStack<TupleView<(Text,Text)>> 와 같은 형태입니다.
- 위와 같이 작성할 순 있으나, 만약 view의 구성이 변경될 때마다 지속적인 업데이트가 필요합니다.
- 따라서, some 키워드를 활용한 불 투명 반환타입으로 전환함으로서 생산성을 높일 수 있습니다.
struct ContentView: View {
var body: some View {
// 여기서 some View가 아닌, 사실 VStack<TupleView<(Text,Text)>> 이다!
VStack {
Text("쉽고 재미있는")
Text("알쏭달쏭 SwiftUI")
}
}
}
SwiftUI의 다양한 기능을 살펴보기에 앞서,
View를 구성하는 코드 중 하나인 some View(불 투명 반환타입)에 대해 살펴보았습니다.
다음 포스팅부터는
보다 본격적으로 앱을 만들기 위한, SwiftUI의 기능과 요소들을 다뤄보겠습니다.
'iOS > SwiftUI' 카테고리의 다른 글
[iOS/SwiftUI] 내가 그린 화면 미리보기, 프리뷰 캔버스(Preview Canvas) (0) | 2023.01.16 |
---|---|
[iOS/SwiftUI] Xcode로 SwiftUI 프로젝트 만들기 (0) | 2022.12.24 |
[iOS/SwiftUI] 기존 UIKit과는 어떤 차별점이 있나요? (0) | 2022.12.11 |
댓글