λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ₯– Bread Basics/Swift

Swift 곡식 λ¬Έμ„œ 정리 - 뢈투λͺ…ν•œ νƒ€μž… (Opaque Types)

by BreadDev 2025. 4. 13.
728x90

μ•ˆλ…•ν•˜μ„Έμš”. μ˜€λŠ˜μ€ Swift의 '뢈투λͺ… νƒ€μž…(Opaque Types)'에 λŒ€ν•΄ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€. 뢈투λͺ… νƒ€μž…μ€ κ°’μ˜ ꡬ체적인 νƒ€μž… 정보λ₯Ό μˆ¨κΈ°λ©΄μ„œλ„ νƒ€μž… μ•ˆμ „μ„±μ„ μœ μ§€ν•˜λŠ” λ„κ΅¬μž…λ‹ˆλ‹€.

πŸ“Œ 뢈투λͺ… νƒ€μž…μ΄ ν•΄κ²°ν•˜λŠ” 문제

λ¨Όμ € 뢈투λͺ… νƒ€μž…μ΄ ν•„μš”ν•œ μ΄μœ μ™€ ν•΄κ²°ν•˜λŠ” 문제λ₯Ό μ΄ν•΄ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. λ‹€μŒκ³Ό 같이 ASCII 문자둜 λ„ν˜•μ„ κ·Έλ¦¬λŠ” κ°„λ‹¨ν•œ 예제λ₯Ό μ‚΄νŽ΄λ΄…μ‹œλ‹€:

protocol Shape {
    func draw() -> String
}

struct Triangle: Shape {
    var size: Int
    func draw() -> String {
        var result: [String] = []
        for length in 1...size {
            result.append(String(repeating: "*", count: length))
        }
        return result.joined(separator: "\n")
    }
}

let smallTriangle = Triangle(size: 3)
print(smallTriangle.draw())
// 좜λ ₯:
// *
// **
// ***

μ—¬κΈ°μ„œ λ„ν˜•μ„ λ³€ν˜•ν•˜λŠ” κΈ°λŠ₯을 μΆ”κ°€ν•΄λ΄…μ‹œλ‹€:

struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}

let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
// 좜λ ₯:
// ***
// **
// *

그리고 두 λ„ν˜•μ„ κ²°ν•©ν•˜λŠ” κΈ°λŠ₯도 μΆ”κ°€ν•΄λ΄…μ‹œλ‹€:

struct JoinedShape<T: Shape, U: Shape>: Shape {
    var top: T
    var bottom: U
    func draw() -> String {
        return top.draw() + "\n" + bottom.draw()
    }
}

let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
print(joinedTriangles.draw())
// 좜λ ₯:
// *
// **
// ***
// ***
// **
// *

μ—¬κΈ°μ„œ 문제점:

μ΄λ ‡κ²Œ μ œλ„€λ¦­μ„ μ‚¬μš©ν•˜λ©΄ λ°˜ν™˜ νƒ€μž…μ— λͺ¨λ“  κ΅¬ν˜„ μ„ΈλΆ€ 정보가 λ…ΈμΆœλ©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ joinedTriangles의 νƒ€μž…μ€ JoinedShape<Triangle, FlippedShape<Triangle>>μž…λ‹ˆλ‹€. 이런 λ³΅μž‘ν•œ νƒ€μž…μ€:

  1. κ΅¬ν˜„ μ„ΈλΆ€ 정보λ₯Ό λ…ΈμΆœμ‹œμΌœ API의 좔상화λ₯Ό κΉ¨λœ¨λ¦½λ‹ˆλ‹€
  2. λ‚΄λΆ€ κ΅¬ν˜„μ΄ λ³€κ²½λ˜λ©΄ 곡개 APIκ°€ 변경될 수 μžˆμŠ΅λ‹ˆλ‹€
  3. μ½”λ“œλ₯Ό μ΄ν•΄ν•˜κΈ° μ–΄λ ΅κ²Œ λ§Œλ“­λ‹ˆλ‹€

πŸ“Œ 뢈투λͺ… νƒ€μž…μœΌλ‘œ ν•΄κ²°ν•˜κΈ°

뢈투λͺ… νƒ€μž…μ€ 이런 문제λ₯Ό ν•΄κ²°ν•©λ‹ˆλ‹€. some Protocol ꡬ문을 μ‚¬μš©ν•˜μ—¬ "νŠΉμ • νƒ€μž…μ„ λ°˜ν™˜ν•˜μ§€λ§Œ κ·Έ νƒ€μž…μ΄ λ¬΄μ—‡μΈμ§€λŠ” μ•Œλ €μ£Όμ§€ μ•Šκ² λ‹€"라고 말할 수 μžˆμŠ΅λ‹ˆλ‹€.

func makeTrapezoid() -> some Shape {
    let top = Triangle(size: 2)
    let middle = Square(size: 2)
    let bottom = FlippedShape(shape: top)
    let trapezoid = JoinedShape(
        top: top,
        bottom: JoinedShape(top: middle, bottom: bottom)
    )
    return trapezoid
}

let trapezoid = makeTrapezoid()
print(trapezoid.draw())
// 좜λ ₯:
// *
// **
// **
// **
// **
// *

μ—¬κΈ°μ„œ makeTrapezoid() ν•¨μˆ˜λŠ” some Shape을 λ°˜ν™˜ν•©λ‹ˆλ‹€. ν•¨μˆ˜μ˜ κ΅¬ν˜„μ€ λ³΅μž‘ν•œ 쀑첩 νƒ€μž…μ„ μ‚¬μš©ν•˜μ§€λ§Œ, ν˜ΈμΆœμžλŠ” 단지 그것이 Shape ν”„λ‘œν† μ½œμ„ μ€€μˆ˜ν•œλ‹€λŠ” κ²ƒλ§Œ μ•Œλ©΄ λ©λ‹ˆλ‹€.

μ œλ„€λ¦­κ³Ό 뢈투λͺ… νƒ€μž…μ„ κ²°ν•©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€:

func flip<T: Shape>(_ shape: T) -> some Shape {
    return FlippedShape(shape: shape)
}

func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
    JoinedShape(top: top, bottom: bottom)
}

let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
print(opaqueJoinedTriangles.draw())
// 좜λ ₯:
// *
// **
// ***
// ***
// **
// *

뢈투λͺ… νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄ ν•¨μˆ˜κ°€ κ΅¬ν˜„ μ„ΈλΆ€ 정보λ₯Ό μˆ¨κΈ°λ©΄μ„œλ„ Shape ν”„λ‘œν† μ½œμ„ μ€€μˆ˜ν•˜λŠ” νŠΉμ • νƒ€μž…μ„ λ°˜ν™˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ£Όμ˜μ‚¬ν•­: λ™μΌν•œ νƒ€μž… λ°˜ν™˜ μš”κ΅¬

뢈투λͺ… νƒ€μž…μ„ λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜μ˜ μ€‘μš”ν•œ μ œμ•½μ‚¬ν•­μ€ 항상 λ™μΌν•œ νƒ€μž…μ„ λ°˜ν™˜ν•΄μ•Ό ν•œλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€. μ•„λž˜ μ½”λ“œλŠ” μ»΄νŒŒμΌλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€:

func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
    if shape is Square {
        return shape // 였λ₯˜: λ°˜ν™˜ νƒ€μž…μ΄ μΌμΉ˜ν•˜μ§€ μ•ŠμŒ
    }
    return FlippedShape(shape: shape) // 였λ₯˜: λ°˜ν™˜ νƒ€μž…μ΄ μΌμΉ˜ν•˜μ§€ μ•ŠμŒ
}

이 ν•¨μˆ˜λŠ” λ•Œλ‘œλŠ” Squareλ₯Ό, λ•Œλ‘œλŠ” FlippedShape<T>λ₯Ό λ°˜ν™˜ν•˜λ €κ³  μ‹œλ„ν•˜κΈ° λ•Œλ¬Έμ— μ—λŸ¬κ°€ λ°œμƒν•©λ‹ˆλ‹€.

πŸ“Œ λ°•μŠ€ν˜• ν”„λ‘œν† μ½œ νƒ€μž…(Boxed Protocol Types)

뢈투λͺ… νƒ€μž…κ³Ό λΉ„μŠ·ν•˜μ§€λ§Œ λ‹€λ₯Έ κ°œλ…μΈ 'λ°•μŠ€ν˜• ν”„λ‘œν† μ½œ νƒ€μž…'을 μ‚΄νŽ΄λ΄…μ‹œλ‹€. Swiftμ—μ„œλŠ” any Protocol ꡬ문을 μ‚¬μš©ν•˜μ—¬ ν‘œν˜„ν•©λ‹ˆλ‹€.

struct VerticalShapes: Shape {
    var shapes: [any Shape]
    func draw() -> String {
        return shapes.map { $0.draw() }.joined(separator: "\n\n")
    }
}

let largeTriangle = Triangle(size: 5)
let largeSquare = Square(size: 5)
let vertical = VerticalShapes(shapes: [largeTriangle, largeSquare])
print(vertical.draw())

μ—¬κΈ°μ„œ [any Shape]λŠ” λ°•μŠ€ν˜• ν”„λ‘œν† μ½œ νƒ€μž…μ˜ λ°°μ—΄μž…λ‹ˆλ‹€. 이 배열은 Shape ν”„λ‘œν† μ½œμ„ μ€€μˆ˜ν•˜λŠ” μ„œλ‘œ λ‹€λ₯Έ νƒ€μž…μ˜ 값듀을 포함할 수 μžˆμŠ΅λ‹ˆλ‹€.

λ°•μŠ€ν˜• ν”„λ‘œν† μ½œ νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄ νƒ€μž… 정보가 λŸ°νƒ€μž„κΉŒμ§€ μ§€μ›Œμ§€λ―€λ‘œ(type erasure), ν•„μš”ν•  λ•Œ νƒ€μž… μΊμŠ€νŒ…μ„ 톡해 μ›λž˜ νƒ€μž…μœΌλ‘œ λ‹€μ‹œ μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€:

if let downcastTriangle = vertical.shapes[0] as? Triangle {
    print(downcastTriangle.size)  // 5 좜λ ₯
}

πŸ“Œ 뢈투λͺ… νƒ€μž…κ³Ό λ°•μŠ€ν˜• ν”„λ‘œν† μ½œ νƒ€μž…μ˜ 차이점

이 두 κ°œλ…μ€ μœ μ‚¬ν•΄ λ³΄μ΄μ§€λ§Œ μ€‘μš”ν•œ 차이점이 μžˆμŠ΅λ‹ˆλ‹€:

1. νƒ€μž… 정체성 μœ μ§€

  • 뢈투λͺ… νƒ€μž…(some Protocol): νŠΉμ • νƒ€μž…μ˜ 정체성을 μœ μ§€ν•©λ‹ˆλ‹€. μ»΄νŒŒμΌλŸ¬λŠ” μ‹€μ œ νƒ€μž…μ„ μ•Œκ³  μžˆμ§€λ§Œ, ν˜ΈμΆœμžμ—κ²ŒλŠ” μˆ¨κΉλ‹ˆλ‹€.
  • λ°•μŠ€ν˜• ν”„λ‘œν† μ½œ νƒ€μž…(any Protocol): νƒ€μž… 정체성을 μ§€μš°κ³  λŸ°νƒ€μž„μ— λ‹€μ–‘ν•œ νƒ€μž…μ„ ν—ˆμš©ν•©λ‹ˆλ‹€.

2. μœ μ—°μ„±κ³Ό μ„±λŠ₯

  • 뢈투λͺ… νƒ€μž…: 컴파일 μ‹œκ°„μ— μ‹€μ œ νƒ€μž…μ΄ κ²°μ •λ˜λ―€λ‘œ μ΅œμ ν™”κ°€ κ°€λŠ₯ν•˜κ³  μ„±λŠ₯이 μ’‹μŠ΅λ‹ˆλ‹€.
  • λ°•μŠ€ν˜• ν”„λ‘œν† μ½œ νƒ€μž…: λŸ°νƒ€μž„ μœ μ—°μ„±μ„ μ œκ³΅ν•˜μ§€λ§Œ κ°„μ ‘ μ°Έμ‘°(λ°•μ‹±)둜 μΈν•œ μ„±λŠ₯ λΉ„μš©μ΄ μžˆμŠ΅λ‹ˆλ‹€.

3. νƒ€μž… μž‘μ—…

λ‹€μŒ μ˜ˆλŠ” λ°˜ν™˜ νƒ€μž…μ΄ λ°•μŠ€ν˜• ν”„λ‘œν† μ½œμΈ ν•¨μˆ˜μž…λ‹ˆλ‹€:

func protoFlip<T: Shape>(_ shape: T) -> any Shape {
    return FlippedShape(shape: shape)
}

// λ‹€λ₯Έ 쑰건에 따라 λ‹€λ₯Έ νƒ€μž… λ°˜ν™˜ κ°€λŠ₯
func protoFlip<T: Shape>(_ shape: T) -> any Shape {
    if shape is Square {
        return shape
    }
    return FlippedShape(shape: shape)
}

let protoFlippedTriangle = protoFlip(smallTriangle)
let sameThing = protoFlip(smallTriangle)
// protoFlippedTriangle == sameThing  // 였λ₯˜: '==' μ—°μ‚° λΆˆκ°€λŠ₯

뢈투λͺ… νƒ€μž…κ³Ό 달리 λ°•μŠ€ν˜• ν”„λ‘œν† μ½œ νƒ€μž…μ€ νƒ€μž… 정보λ₯Ό λ³΄μ‘΄ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— == 같은 μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€. λ˜ν•œ νƒ€μž… 정보가 μ§€μ›Œμ§€κΈ° λ•Œλ¬Έμ— μ—°κ΄€ νƒ€μž…(Associated Types)이 μžˆλŠ” ν”„λ‘œν† μ½œκ³Ό ν•¨κ»˜ μ‚¬μš©ν•˜λŠ” 데 μ œμ•½μ΄ μžˆμŠ΅λ‹ˆλ‹€.

4. μ—°κ΄€ νƒ€μž…μ΄ μžˆλŠ” ν”„λ‘œν† μ½œ

뢈투λͺ… νƒ€μž…μ€ μ—°κ΄€ νƒ€μž…μ΄ μžˆλŠ” ν”„λ‘œν† μ½œμ„ λ°˜ν™˜ νƒ€μž…μœΌλ‘œ μ‚¬μš©ν•  수 있게 ν•΄μ€λ‹ˆλ‹€:

protocol Container {
    associatedtype Item
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
extension Array: Container { }

// 였λ₯˜: μ—°κ΄€ νƒ€μž…μ΄ μžˆλŠ” ν”„λ‘œν† μ½œμ€ λ°˜ν™˜ νƒ€μž…μœΌλ‘œ μ‚¬μš©ν•  수 μ—†μŒ
func makeProtocolContainer<T>(item: T) -> Container {
    return [item]
}

// 뢈투λͺ… νƒ€μž…μœΌλ‘œ ν•΄κ²°
func makeOpaqueContainer<T>(item: T) -> some Container {
    return [item]
}

let opaqueContainer = makeOpaqueContainer(item: 12)
let twelve = opaqueContainer[0]  // Int νƒ€μž…
print(type(of: twelve))  // "Int" 좜λ ₯

이 μ˜ˆμ œμ—μ„œ 뢈투λͺ… νƒ€μž…μ€ μ—°κ΄€ νƒ€μž…μ΄ μžˆλŠ” ν”„λ‘œν† μ½œμ„ λ°˜ν™˜ νƒ€μž…μœΌλ‘œ μ‚¬μš©ν•  수 있게 ν•΄μ£Όλ©°, νƒ€μž… μΆ”λ‘ κΉŒμ§€ κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.

πŸ“Œ 뢈투λͺ… νŒŒλΌλ―Έν„° νƒ€μž…(Opaque Parameter Types)

ν•¨μˆ˜ νŒŒλΌλ―Έν„°μ—μ„œλ„ some Protocol ꡬ문을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

func drawTwiceSome(_ shape: some Shape) -> String {
    let drawn = shape.draw()
    return drawn + "\n" + drawn
}

κ·ΈλŸ¬λ‚˜ μ€‘μš”ν•œ 차이점이 μžˆμŠ΅λ‹ˆλ‹€. νŒŒλΌλ―Έν„° νƒ€μž…μ—μ„œ some을 μ‚¬μš©ν•˜λŠ” 것은 μ§„μ •ν•œ 뢈투λͺ… νƒ€μž…μ΄ μ•„λ‹ˆλΌ μ œλ„€λ¦­μ˜ κ°„νŽΈ κ΅¬λ¬Έμž…λ‹ˆλ‹€. μœ„ ν•¨μˆ˜λŠ” λ‹€μŒκ³Ό λ™μΌν•©λ‹ˆλ‹€:

func drawTwiceGeneric<SomeShape: Shape>(_ shape: SomeShape) -> String {
    let drawn = shape.draw()
    return drawn + "\n" + drawn
}

μ—¬λŸ¬ νŒŒλΌλ―Έν„°μ— some을 μ‚¬μš©ν•˜λ©΄ 각각은 독립적인 μ œλ„€λ¦­ νƒ€μž…μœΌλ‘œ κ°„μ£Όλ©λ‹ˆλ‹€:

func combine(shape s1: some Shape, with s2: some Shape) -> String {
    return s1.draw() + "\n" + s2.draw()
}

// μ„œλ‘œ λ‹€λ₯Έ νƒ€μž…μœΌλ‘œ 호좜 κ°€λŠ₯
combine(shape: smallTriangle, with: trapezoid)

이 κ°„νŽΈ ꡬ문은 μ œλ„€λ¦­ where μ ˆμ΄λ‚˜ 동일 νƒ€μž… μ œμ•½(==)을 μ§€μ›ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ, λ³΅μž‘ν•œ μ œμ•½μ΄ ν•„μš”ν•œ κ²½μš°μ—λŠ” 전톡적인 μ œλ„€λ¦­ ꡬ문을 μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

πŸ“Œ μ‹€μ „ μ‚¬μš© μ˜ˆμ‹œμ™€ ꢌμž₯ 사항

뢈투λͺ… νƒ€μž… μ‚¬μš©μ΄ 쒋은 경우:

  1. 라이브러리/ν”„λ ˆμž„μ›Œν¬ API λ””μžμΈ: κ΅¬ν˜„ μ„ΈλΆ€ μ •λ³΄λŠ” 숨기되 νƒ€μž… μ•ˆμ „μ„±μ€ μœ μ§€ν•˜κ³  싢을 λ•Œ
  2. λ™μΌν•œ νƒ€μž…μ„ 보μž₯ν•΄μ•Ό ν•˜λŠ” 경우: ν•¨μˆ˜κ°€ 항상 λ™μΌν•œ ꡬ체적 νƒ€μž…μ„ λ°˜ν™˜ν•΄μ•Ό ν•  λ•Œ
  3. μ—°κ΄€ νƒ€μž…μ΄ μžˆλŠ” ν”„λ‘œν† μ½œ: μ œλ„€λ¦­ μ œμ•½μœΌλ‘œ μ‚¬μš©ν•˜κΈ° μ–΄λ €μš΄ ν”„λ‘œν† μ½œμ„ λ°˜ν™˜ν•΄μ•Ό ν•  λ•Œ
  4. 컴파일 μ‹œκ°„ μ΅œμ ν™”κ°€ μ€‘μš”ν•œ 경우: μ„±λŠ₯이 μ€‘μš”ν•˜κ³  νƒ€μž… 정보가 ν•„μš”ν•  λ•Œ

λ°•μŠ€ν˜• ν”„λ‘œν† μ½œ νƒ€μž… μ‚¬μš©μ΄ 쒋은 경우:

  1. 이쒅 μ»¬λ ‰μ…˜: λ‹€μ–‘ν•œ νƒ€μž…μ˜ 객체λ₯Ό ν•˜λ‚˜μ˜ μ»¬λ ‰μ…˜μ— μ €μž₯ν•΄μ•Ό ν•  λ•Œ
  2. λŸ°νƒ€μž„μ— νƒ€μž…μ΄ κ²°μ •λ˜λŠ” 경우: μ‚¬μš©μž μž…λ ₯μ΄λ‚˜ 동적 데이터에 따라 λ‹€λ₯Έ νƒ€μž…μ„ μ‚¬μš©ν•΄μ•Ό ν•  λ•Œ
  3. νƒ€μž… 정체성이 μ€‘μš”ν•˜μ§€ μ•Šμ€ 경우: λ‹€μ–‘ν•œ νƒ€μž…μ˜ 객체λ₯Ό μ²˜λ¦¬ν•˜λŠ” μ½”λ“œμ—μ„œ

πŸ“Œ μš”μ•½

뢈투λͺ… νƒ€μž…(some Protocol)은 Swift의 κ°•λ ₯ν•œ κΈ°λŠ₯으둜, κ΅¬ν˜„ μ„ΈλΆ€ 정보λ₯Ό μˆ¨κΈ°λ©΄μ„œλ„ νƒ€μž… μ•ˆμ „μ„±μ„ μœ μ§€ν•  수 있게 ν•΄μ€λ‹ˆλ‹€. μ΄λŠ” μ œλ„€λ¦­κ³Ό κ²°ν•©ν•˜μ—¬ APIλ₯Ό λ”μš± ν‘œν˜„λ ₯ 있고 μœ μ—°ν•˜κ²Œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

반면 λ°•μŠ€ν˜• ν”„λ‘œν† μ½œ νƒ€μž…(any Protocol)은 λŸ°νƒ€μž„ μœ μ—°μ„±μ„ μ œκ³΅ν•˜μ§€λ§Œ νƒ€μž… 정보λ₯Ό μ§€μš°λ―€λ‘œ 일뢀 νƒ€μž… 기반 μž‘μ—…μ΄ μ œν•œλ©λ‹ˆλ‹€.