🔥 박스형 타입
박스형 프로토콜 타입은 "실존 타입(existential type)"이라고도 불리는데, "프로토콜을 준수하는 타입 T
가 존재한다"는 의미에서 유래했습니다. 박스형 프로토콜 타입을 만들려면 프로토콜 이름 앞에 any
를 붙이면 됩니다. 다음은 그 예시입니다.
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())
swift
위의 예제에서 VerticalShapes
는 shapes
의 타입을 [any Shape]
로 선언합니다. 이는 박스형 Shape
요소의 배열이죠. 배열의 각 요소는 서로 다른 타입일 수 있으며, 각 타입은 Shape
프로토콜을 준수해야 합니다. Swift는 이런 런타임 유연성을 지원하기 위해 필요할 때 간접 참조 레벨을 추가하는데, 이를 "박스(box)"라고 하며 성능 비용이 발생합니다.
VerticalShapes
내에서는 Shape
프로토콜에서 요구하는 메서드, 속성 및 서브스크립트를 사용할 수 있습니다. 예를 들어, draw()
메서드는 배열의 각 요소에 대해 Shape
에서 요구하는 draw()
메서드를 호출합니다. 반면, Shape
에서 요구하지 않는 속성이나 메서드에는 접근할 수 없습니다.
shapes
에 사용할 수 있는 세 가지 타입을 비교해 보겠습니다:
- 제네릭을 사용하여
struct VerticalShapes<S: Shape>
및var shapes: [S]
를 작성하면 요소가 특정 도형 타입인 배열이 만들어지며, 해당 특정 타입의 정체는 배열과 상호 작용하는 모든 코드에 표시됩니다. - 불투명 타입을 사용하여
var shapes: [some Shape]
를 작성하면 요소가 특정 도형 타입인 배열이 만들어지지만, 해당 특정 타입의 정체는 숨겨집니다. - 박스형 프로토콜 타입을 사용하여
var shapes: [any Shape]
를 작성하면 서로 다른 타입의 요소를 저장할 수 있는 배열이 만들어지며, 해당 타입의 정체는 숨겨집니다.
제네릭을 사용하는 경우:
struct VerticalShapes<S: Shape> { var shapes: [S] func draw() -> String { return shapes.map { $0.draw() }.joined(separator: "\n\n") } } let triangles = VerticalShapes(shapes: [Triangle(size: 3), Triangle(size: 4)]) print(triangles.draw())
swift
이 경우 VerticalShapes
는 특정 도형 타입 S
로 매개변수화되며, shapes
배열은 해당 타입의 요소만 포함할 수 있습니다. 예를 들어, VerticalShapes<Triangle>
은 Triangle
인스턴스만 저장할 수 있고, draw()
메서드 내에서 S
타입의 모든 속성과 메서드에 접근할 수 있습니다.
불투명 타입을 사용하는 경우:
struct VerticalShapes { var shapes: [some Shape] func draw() -> String { return shapes.map { $0.draw() }.joined(separator: "\n\n") } } let shapes = VerticalShapes(shapes: [Triangle(size: 3), Square(size: 4)]) print(shapes.draw())
swift
이 경우 shapes
배열은 특정 도형 타입의 요소만 포함할 수 있지만, 해당 타입은 외부에서 볼 수 없습니다. draw()
메서드 내에서는 Shape
프로토콜의 요구사항인 draw()
메서드만 접근 가능합니다.
박스형 프로토콜 타입을 사용하는 경우:
struct VerticalShapes { var shapes: [any Shape] func draw() -> String { return shapes.map { $0.draw() }.joined(separator: "\n\n") } } let shapes = VerticalShapes(shapes: [Triangle(size: 3), Square(size: 4), Circle(radius: 5)]) print(shapes.draw())
swift
이 경우 shapes
배열은 Shape
프로토콜을 준수하는 모든 타입의 요소를 포함할 수 있습니다. 예를 들어, Triangle
, Square
, Circle
등 서로 다른 도형 타입을 함께 저장할 수 있죠. draw()
메서드 내에서는 Shape
프로토콜의 요구사항인 draw()
메서드만 접근할 수 있습니다.
박스형 값의 기본 타입을 알고 있다면 as?
다운캐스트를 사용할 수 있습니다. 예를 들면 다음과 같죠.
if let downcastTriangle = vertical.shapes[0] as? Triangle { print(downcastTriangle.size) } // "5" 출력
swift
더 자세한 정보는 다운캐스팅을 참조하세요.
불투명 타입(some Shape
)을 사용하면 컬렉션에 저장된 요소의 실제 타입 정보가 숨겨지기 때문에 as?
를 사용한 다운캐스팅이 불가능하며, Shape
프로토콜에서 정의된 멤버만 접근할 수 있습니다.
반면에 박스형 프로토콜 타입(any Shape
)을 사용하면 컬렉션에 저장된 요소의 실제 타입 정보는 런타임에 유지되므로 as?
를 사용하여 원래 타입으로 다운캐스팅할 수 있습니다. 다운캐스팅에 성공하면 draw()
메서드뿐만 아니라 해당 타입의 다른 속성이나 메서드에도 접근할 수 있습니다.
struct VerticalShapes { var shapes: [any Shape] func draw() -> String { return shapes.map { $0.draw() }.joined(separator: "\n\n") } } let shapes = VerticalShapes(shapes: [Triangle(size: 3), Square(size: 4), Circle(radius: 5)]) if let triangle = shapes.shapes[0] as? Triangle { print("Triangle size: \(triangle.size)") } else { print("Not a triangle") }
swift
하지만 some Shape
컬렉션에서는 이런 다운캐스팅이 불가능해요:
struct VerticalShapes { var shapes: [some Shape] func draw() -> String { return shapes.map { $0.draw() }.joined(separator: "\n\n") } } let shapes = VerticalShapes(shapes: [Triangle(size: 3), Square(size: 4)]) if let triangle = shapes.shapes[0] as? Triangle { print("Triangle size: \(triangle.size)") } else { print("Not a triangle") } // 컴파일 에러: 'some' type cannot be used with 'as?' operator
swift
any
타입과 some
타입은 모두 타입 정보를 숨기지만, any
는 런타임에 타입 정보를 유지하여 다운캐스팅을 허용하는 반면, some
은 컴파일 타임에 타입 정보를 완전히 숨겨 다운캐스팅을 불가능하게 만듭니다.
선택은 사용 사례에 따라 달라질 수 있습니다. 동일한 타입의 도형만 다루면서 해당 타입의 모든 기능을 사용해야 한다면 제네릭이 적합할 수 있습니다. 특정 도형 타입을 숨기면서 Shape
프로토콜의 기능만 사용하려면 불투명 타입을 선택할 수 있죠. 그리고 다양한 도형 타입을 함께 저장하고 관리해야 한다면 박스형 프로토콜 타입이 최선의 선택이 될 것입니다. 상황에 따라 적절한 방식을 선택하는 것이 중요합니다.