πŸŒ₯️ SwiftUI의 Opaque Typeκ³Ό some/any에 λŒ€ν•΄ νŒŒν—€μΉ˜κΈ°!

SeBinΒ·2025λ…„ 4μ›” 13일
post-thumbnail

SwiftUI둜 앱을 λ§Œλ“€λ‹€ 보면, bodyμ—μ„œ λ·°λ₯Ό λ°˜ν™˜ν•  λ•Œ μ’…μ’… some ViewλΌλŠ” ν‘œν˜„μ„ 보게 λ©λ‹ˆλ‹€. some 은 λ„λŒ€μ²΄ λ­˜κΉŒμš”?
그리고 any, AnyView, @ViewBuilderλŠ” μ–Έμ œ 써야 ν• κΉŒμš”? 이에 λŒ€ν•΄ νŒŒν—€μ³λ³΄μ•˜μŠ΅λ‹ˆλ‹€!

Opaque Typeμ΄λž€

Opaque Type: 뢈투λͺ… νƒ€μž…

ν•œκ΅­λ§λ‘œλŠ” 뢈투λͺ… νƒ€μž…μ΄λΌκ³  ν•˜λŠ”λ°μš”, SwiftUIμ—μ„œ some ν‚€μ›Œλ“œλ₯Ό 뢙인 νƒ€μž…μ„ Opaque Type이라고 ν•©λ‹ˆλ‹€!

struct BasicView: View {
    var body: some View {
        Text("Hello, World!")
            .bold()
            .background(.yellow)
            .foregroundStyle(.red)
            .padding()
            .background(.blue)
            .jack()
    }
}
  • 보톡 λ·°λ₯Ό λ§Œλ“€λ©΄ 기본적으둜 body ν”„λ‘œνΌν‹°μ˜ νƒ€μž…μ΄ some View 둜 λ˜μ–΄μžˆλŠ” κ±Έ λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

μœ„ μ½”λ“œλ₯Ό λ³΄μ‹œλ©΄ Text에 μ—¬λŸ¬ View Modifierκ°€ 체이닝 λ˜μ–΄μžˆλŠ” κ±Έ λ³Ό 수 μžˆλŠ”λ° μ–΄μ°¨ν”Ό body μ—°μ‚° ꡬ문 내에 Text밖에 μ—†λŠ” κ±° some View λŒ€μ‹  Text λ₯Ό μ“°λ©΄ μ•ˆλ˜λŠ” κ±ΈκΉŒμš”? μ™œ ꡳ이 some ν‚€μ›Œλ“œλ₯Ό λΆ™μ—¬μ„œ Opaque Typeμ΄λΌλŠ” 것을 μ‚¬μš©ν•˜λŠ” κ±ΈκΉŒμš”?

print(Mirror(reflecting: self).subjectType)
  • 이걸 μ‚¬μš©ν•˜λ©΄ λŸ°νƒ€μž„ μ‹œμ μ— μ–΄λ–€ νƒ€μž…μΈμ§€ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. 이걸둜 Text의 νƒ€μž…μ„ 확인해보면..
ModifiedContent<ModifiedContent<ModifiedContent<ModifiedContent<Text, _BackgroundStyleModifier<Color>>, _ForegroundStyleModifier<Color>>, _PaddingLayout>, _BackgroundStyleModifier<Color>>

κ²°κ³ΌλŠ” μ΄λ ‡κ²Œ λ‚˜μ˜΅λ‹ˆλ‹€.. View Modifier을 뢙이면 λΆ™μ΄λŠ” λŒ€λ‘œ 죄닀 νƒ€μž…λͺ…이 되고 μ œλ„€λ¦­μ˜ μ œλ„€λ¦­μ˜ μ œλ„€λ¦­μ΄ λ˜μ–΄.. μ•„μ£Ό κΈΈκ³  λ³΅μž‘ν•˜κ²Œ λ‚˜μ˜΅λ‹ˆλ‹€..!

이걸 νƒ€μž…λͺ…μœΌλ‘œ μ“Έ μˆ˜λŠ” μžˆμ§€λ§Œ κ·Έλ ‡κ²Œ ν•˜κΈ°μ—” λ„ˆλ¬΄ λ³΅μž‘ν•˜κΈ° λ•Œλ¬Έμ— 이런 λ³΅μž‘ν•œ νƒ€μž…λͺ…을 숨기고자 μ‚¬μš©ν•˜λŠ” 것이 some View (Opaque Type) μž…λ‹ˆλ‹€!

Opaque Type의 μ„±λŠ₯ 이점

Opaque Type은 νƒ€μž…μ„ μˆ¨κΈ΄λ‹€κ³  ν–ˆλŠ”λ° 그건 개발자 λˆˆμ—λ§Œ 숨기고, μ»΄νŒŒμΌλŸ¬μ—κ²ŒλŠ” μ•„μ£Ό 투λͺ…ν•˜κ²Œ 잘 λ³΄μž…λ‹ˆλ‹€.

κ·Έλž˜μ„œ 컴파일 μ‹œμ μ— νƒ€μž…μ΄ ν™•μ •λ˜μ–΄ μ΄λŠ” 정적 λ””μŠ€νŒ¨μΉ˜(static dispatch)둜 λ™μž‘ν•  수 있게 ν•˜κΈ° λ•Œλ¬Έμ— μ΅œμ ν™”κ°€ κ°€λŠ₯ν•΄μ§‘λ‹ˆλ‹€!

private func textButton() -> some View {
    VStack {
        Image(systemName: "star")
        Text("λ²„νŠΌ")
    }
}

κ΅¬ν˜„λΆ€μ—μ„œ some View λ₯Ό λ°˜ν™˜ν•˜μ§€λ§Œ 이건 ꡬ체 νƒ€μž…μ„ λ°˜ν™˜ν•˜κΈ° λ•Œλ¬Έμ— μ»΄νŒŒμΌλŸ¬κ°€ κ·Έ νƒ€μž…μ„ μ •ν™•νžˆ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ œλ„€λ¦­κ³Ό 달리 κ΅¬ν˜„λΆ€μ— ꡬ체적 νƒ€μž…μ΄ λ“€μ–΄κ°€κ³  ν˜ΈμΆœλΆ€λ₯Ό μΆ”μƒν™”μ‹œν‚€κΈ° λ•Œλ¬Έμ— μ—­μ œλ„€λ¦­μ΄λΌκ³ λ„ λΆ€λ¦…λ‹ˆλ‹€.

Opaque Type (some)과 Existential Type (any)의 차이점

Swiftμ—μ„œλŠ” someκ³Ό any λ‘˜ λ‹€ ν”„λ‘œν† μ½œ νƒ€μž…μ„ ν‘œν˜„ν•  λ•Œ μ‚¬μš©λ˜μ§€λ§Œ, μ—­ν• κ³Ό λ™μž‘ 방식이 μ™„μ „νžˆ λ‹€λ¦…λ‹ˆλ‹€.

func getRandomView() -> any View {
    if Bool.random() {
        return Text("Hello")
    } else {
        return Button("Click me") {}
    }
}

any ViewλŠ” Swift 5.7λΆ€ν„° λ„μž…λœ 쑴재 νƒ€μž…(existential type)을 λͺ…μ‹œμ μœΌλ‘œ ν‘œν˜„ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€. μ΄μ „μ—λŠ” κ·Έλƒ₯ View라고 λ‹¨μˆœνžˆ ν”„λ‘œν† μ½œ ν˜•νƒœλ‘œ 썼닀면, μ΄μ œλŠ” any View둜 ν‘œν˜„ν•©λ‹ˆλ‹€.

μ΄λ ‡κ²Œ ν•˜λ©΄ μ»΄νŒŒμΌλŸ¬κ°€ ν•΄λ‹Ή νƒ€μž…μ΄ 동적 λ””μŠ€νŒ¨μΉ˜λ₯Ό μ‚¬μš©ν•˜λŠ” 쑴재 νƒ€μž…μ΄λΌλŠ” 것을 λͺ…ν™•ν•˜κ²Œ 인식할 수 있게 λ©λ‹ˆλ‹€!

쑴재 νƒ€μž…(existential type)

"ν•΄λ‹Ή ν”„λ‘œν† μ½œμ„ μ€€μˆ˜ν•˜λŠ” νƒ€μž…μ΄Β μ‘΄μž¬ν•œλ‹€"λΌλŠ”Β μ˜λ―Έ

Swiftμ—μ„œ ν”„λ‘œν† μ½œμ„ νƒ€μž…μœΌλ‘œ μ‚¬μš©ν•  λ•Œ μƒκΈ°λŠ” κ°œλ…μœΌλ‘œ, λ°˜ν™˜ κ°’μ΄Β νŠΉμ • ν”„λ‘œν† μ½œμ„ μ€€μˆ˜ν•œλ‹€λŠ” μ‚¬μ‹€λ§Œ 보μž₯ν•˜κ³ Β κ΅¬μ²΄μ μΈ νƒ€μž…μ€ μˆ¨κΈ°λŠ”Β λ°©μ‹μž…λ‹ˆλ‹€.

μ‹€μ œ μ‚¬μš©λ˜λŠ” ꡬ체 νƒ€μž…μ— λŒ€ν•΄μ„œλŠ”Β λŸ°νƒ€μž„μ— μ•Œ 수 μžˆμ–΄μ„œ μ„±λŠ₯적으둜 λΆˆλ¦¬ν•œ 점이 μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€.

some - Opaque Type

  • λ°˜ν™˜ νƒ€μž…μ΄ λͺ…ν™•ν•˜κ³  κ³ μ •λœ 단일 νƒ€μž…μ΄λΌλŠ” 것을 μ»΄νŒŒμΌλŸ¬κ°€ μ•Œκ³  있음.
  • 정적 λ””μŠ€νŒ¨μΉ˜λ₯Ό 톡해 μ„±λŠ₯이 ν–₯상됨.
  • λ·° ꡬ성 μ‹œ νƒ€μž… μ•ˆμ •μ„±μ„ 확보할 수 있음.

any - Existential Type

  • λŸ°νƒ€μž„κΉŒμ§€ μ–΄λ–€ νƒ€μž…μΈμ§€ μ •ν•΄μ§€μ§€ μ•ŠμŒ.
  • λ‹€μ–‘ν•œ νƒ€μž…μ„ ν•œ κ³³μ—μ„œ 닀뀄야 ν•  λ•Œ μ‚¬μš©ν•˜μ§€λ§Œ, λΉ„μš©μ΄ 큼.
  • λ‚΄λΆ€ κ΅¬ν˜„μ€ 동적 λ””μŠ€νŒ¨μΉ˜, 즉 λŸ°νƒ€μž„ νƒ€μž… 체크λ₯Ό 수반.

anyλ₯Ό λ„μž…ν•˜κ²Œ 된 이유

μ•„κΉŒ ν•΄λ‹Ή νƒ€μž…μ΄ 동적 λ””μŠ€νŒ¨μΉ˜λ₯Ό μ‚¬μš©ν•˜λŠ” 쑴재 νƒ€μž…μ΄λΌλŠ” 것을 λͺ…ν™•ν•˜κ²Œ 인식할 수 있기 λ•Œλ¬Έμ΄λΌκ³  ν–ˆλŠ”λ°μš”, μ’€ 더 μžμ„Ένžˆ μ‚΄νŽ΄λ³΄μžλ©΄

1. Existential νƒ€μž…μ˜ λͺ…ν™•ν•œ ν‘œν˜„

var view: View // Swift 5.6 이전
var view: any View

μœ„ μ½”λ“œμ—μ„œ viewλŠ” λ‚΄λΆ€μ μœΌλ‘œ existential container둜 μ²˜λ¦¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 즉, λ‹€μ–‘ν•œ View νƒ€μž…μ„ 담을 수 μžˆλŠ” μΌμ’…μ˜ 래퍼 νƒ€μž…μΈλ°, 컴파일 νƒ€μž„μ—λŠ” ꡬ체 νƒ€μž…μ„ μ•Œ 수 μ—†μŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ existential νƒ€μž… μ‚¬μš©μ΄ λŸ°νƒ€μž„ λΉ„μš©μ„ μœ λ°œν•  수 μžˆλ‹€λŠ” 점, 그리고 일반적인 μ œλ„€λ¦­ νƒ€μž…κ³Ό κ΅¬λΆ„λ˜μ§€ μ•ŠλŠ”λ‹€λŠ” 점이 ν˜Όλž€μ„ μΌμœΌμΌ°μŠ΅λ‹ˆλ‹€.

κ·Έλž˜μ„œ any λ₯Ό λΆ™μž„μœΌλ‘œμ¨ ꡬ체 νƒ€μž…μ΄ μ•„λ‹Œ ν”„λ‘œν† μ½œμ„ νƒ€μž…μœΌλ‘œ μ“΄λ‹€λŠ” 의미λ₯Ό λͺ…ν™•νžˆ 전달할 수 μžˆμŠ΅λ‹ˆλ‹€.

2. Generic vs Existential ν˜Όλž€ λ°©μ§€

// Generic: ꡬ체 νƒ€μž…μ€ 컴파일 νƒ€μž„μ— 결정됨
func render<V: View>(_ view: V) { ... }

// Existential: μ–΄λ–€ Viewλ“  담을 수 있음 (μ»΄νŒŒμΌλŸ¬λŠ” λ‚΄λΆ€ νƒ€μž…μ„ λͺ¨λ¦„)
func render(_ view: any View) { ... }

κ³Όκ±°μ—λŠ” View만 써도 existentialμ΄μ—ˆκΈ° λ•Œλ¬Έμ— 이 λ‘˜μ΄ λΉ„μŠ·ν•΄ λ³΄μ˜€μ§€λ§Œ, μ‹€μ œλ‘œλŠ” νΌν¬λ¨ΌμŠ€μ™€ νƒ€μž… μ‹œμŠ€ν…œμ—μ„œ λ‹€λ₯΄κ²Œ μž‘λ™ν–ˆμŠ΅λ‹ˆλ‹€.

any ν‚€μ›Œλ“œλŠ” 이 차이λ₯Ό λͺ…ν™•νžˆ λ³΄μ—¬μ£ΌλŠ” 역할을 ν•©λ‹ˆλ‹€.

3. μ œμ•½μ΄ μžˆλŠ” ν”„λ‘œν† μ½œ μ‚¬μš©μ— λŒ€ν•œ μ œν•œ 인식

associatedtypeμ΄λ‚˜ Selfλ₯Ό ν¬ν•¨ν•˜λŠ” ν”„λ‘œν† μ½œμ€ μ—¬μ „νžˆ anyλ₯Ό 뢙여도 쑴재 νƒ€μž…μœΌλ‘œ μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

이런 ν”„λ‘œν† μ½œμ€ λ‚΄λΆ€μ μœΌλ‘œ ꡬ체적인 νƒ€μž…μ΄ κ²°μ •λ˜μ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ—, μ»΄νŒŒμΌλŸ¬κ°€ ν•΄λ‹Ή νƒ€μž…μ„ μΆ”λ‘ ν•  수 μ—†λŠ” μƒν™©μ—μ„œλŠ” νƒ€μž…μœΌλ‘œ μ‚¬μš©ν•  수 μ—†λŠ” 것이죠.

예λ₯Ό λ“€μ–΄ λ‹€μŒκ³Ό 같은 ν”„λ‘œν† μ½œμ€ anyλ₯Ό 뢙여도 μ—λŸ¬κ°€ λ°œμƒν•©λ‹ˆλ‹€:

protocol IdentifiableItem {
    associatedtype ID
    var id: ID { get }
}

let item: any IdentifiableItem // 컴파일 μ—λŸ¬

Swift 5.7 μ΄μ „μ—λŠ” 이런 μ œμ•½μœΌλ‘œ 인해 ν—·κ°ˆλ¦¬κ±°λ‚˜, λͺ…ν™•ν•˜μ§€ μ•Šμ€ μ—λŸ¬ λ©”μ‹œμ§€κ°€ 좜λ ₯λ˜λŠ” κ²½μš°λ„ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ any λ„μž… μ΄ν›„μ—λŠ” β€œμ΄ ν”„λ‘œν† μ½œμ€ 쑴재 νƒ€μž…μœΌλ‘œ μ‚¬μš©ν•  수 μ—†λ‹€"λŠ” κ±Έ 컴파일 νƒ€μž„μ— ν™•μ‹€ν•˜κ²Œ μ•Œλ €μ€λ‹ˆλ‹€.

덕뢄에 κ°œλ°œμžλŠ” μ–΄λ–€ ν”„λ‘œν† μ½œμ΄ μ œλ„€λ¦­μœΌλ‘œλ§Œ μ‚¬μš© κ°€λŠ₯ν•œμ§€, μ–΄λ–€ ν”„λ‘œν† μ½œμ΄ 쑴재 νƒ€μž…μœΌλ‘œ μ“Έ 수 μžˆλŠ”μ§€λ₯Ό 훨씬 μ§κ΄€μ μœΌλ‘œ ꡬ뢄할 수 μžˆμŠ΅λ‹ˆλ‹€.

any View vs AnyView

any ViewλŠ” Swift 5.7 이후 λ“±μž₯ν•œ μƒˆλ‘œμš΄ νƒ€μž… μ„ μ–Έ 방식이라면,
AnyViewλŠ” SwiftUIκ°€ μ œκ³΅ν•˜λŠ” νƒ€μž… μ§€μš°κΈ°(type-erasing) κ΅¬μ‘°μ²΄μž…λ‹ˆλ‹€.

// Swift 5.6 이전 방식 (νƒ€μž… μ§€μš°κΈ°)
var body: some View {
    AnyView(condition ? Text("A") : Image(systemName: "star"))
}

// Swift 5.7 이후 방식
func someFunc() -> any View {
    // ...
}

AnyViewλŠ” νƒ€μž… 정체성을 μ§€μš°λ©°, λ‹€μ–‘ν•œ ꡬ체적 νƒ€μž…λ“€μ„ 단일 νƒ€μž…μœΌλ‘œ λž˜ν•‘ν•©λ‹ˆλ‹€. AnyView μ—­μ‹œ λŸ°νƒ€μž„μ— λ™μž‘ν•˜μ—¬ μ„±λŠ₯이 λ–¨μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.

SwiftUI μ½”λ“œ μ΅œμ ν™”ν•˜κΈ°

@ViewBuilder ν™œμš©

any Viewλ‚˜ AnyViewλŠ” 동적 Viewλ₯Ό μ²˜λ¦¬ν•  λ•Œ μœ μš©ν•˜μ§€λ§Œ, μ„±λŠ₯ λΉ„μš©μ΄ ν½λ‹ˆλ‹€. λŒ€μ‹  @ViewBuilder λ₯Ό μ‚¬μš©ν•˜λ©΄ νƒ€μž… μ•ˆμ •μ„±κ³Ό μ„±λŠ₯을 μœ μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

@ViewBuilder
func conditionalView(_ condition: Bool) -> some View {
    if condition {
        Text("A")
    } else {
        Image(systemName: "star")
    }
}

@ViewBuilderλŠ” μ»΄νŒŒμΌλŸ¬κ°€ View 계측을 단일 some View둜 ν†΅ν•©ν•˜λ©°, 정적 λ””μŠ€νŒ¨μΉ˜λ₯Ό 보μž₯ν•©λ‹ˆλ‹€.

struct ColorHStack<Content>: View where Content: View {
    
    let content: () -> Content
    let color: Color
    
    init(color: Color = .clear,
         @ViewBuilder content: @escaping () -> Content) {
        self.color = color
        self.content = content
    }

    var body: some View {
        HStack {
            content()
        }
        .background(color)
    }
}
struct ContentView: View {
                
    var body: some View {
        ColorHStack(color: .purple) {
            Text("Zedd")
            Text("Zedd")
        }
    }
}

μ΄λŸ°μ‹μœΌλ‘œλ„ ν™œμš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

마무리

μ§€κΈˆκΉŒμ§€ SwiftUI의 Opaque Type λΆ€ν„° some, any, AnyView, @ViewBuilderκΉŒμ§€ μ•Œμ•„λ³΄μ•˜λŠ”λ°μš”,

μ •λ¦¬ν•˜μžλ©΄ SwiftUI의 Opaque Type은 Swift의 정적 νƒ€μž… μ‹œμŠ€ν…œκ³Ό 컴파일러 μ΅œμ ν™”μ˜ ν•΅μ‹¬μž…λ‹ˆλ‹€.

some ViewλŠ” 컴파일 νƒ€μž„μ— νƒ€μž…μ„ μΆ”λ‘ ν•˜μ—¬ μ„±λŠ₯κ³Ό μ•ˆμ •μ„±μ„ ν™•λ³΄ν•˜κ³  κ°œλ°œμžλŠ” 선언적 UIλ₯Ό κ°„κ²°νžˆ μž‘μ„±ν•  수 있게 ν•©λ‹ˆλ‹€!

μ•žμœΌλ‘œλŠ” any ν‚€μ›Œλ“œλ‘œ 쑴재 νƒ€μž…μž„μ„ λͺ…μ‹œν•˜κ³  any, AnyView보단 @ViewBuilder둜 μ΅œμ ν™” ν•˜λŠ” 것을 μΆ”μ²œλ“œλ¦½λ‹ˆλ‹€.

μ½μ–΄μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€!

0개의 λŒ“κΈ€