Swift의 형식 지우개 (Type Eraser)
형식 지우개(Type Eraser)는 특정 프로토콜이나 제네릭 타입을 감싸서 구체적인 타입 정보를 숨기고, 대신 공통된 인터페이스로 다룰 수 있게 만드는 디자인 패턴입니다. Swift의 엄격한 타입 시스템은 안전하지만, 경우에 따라 다양한 타입을 동적으로 처리해야 할 때 불편할 수 있습니다. 형식 지우개는 이런 문제를 해결하는 도구로, 동적이고 유연한 코드를 작성할 수 있게 도와줍니다.
형식 지우개의 원리
형식 지우개는 내부적으로 다음의 두 가지를 활용합니다:
- 프로토콜: 공통된 기능을 정의하는 인터페이스.
- 클래스나 구조체: 구체적인 타입 정보를 감싸서 추상화하는 역할.
형식 지우개는 구체적인 타입의 메서드와 속성을 내부에서 호출하고, 호출된 결과를 외부로 제공하여 마치 하나의 타입처럼 동작하게 만듭니다.
Swift에서 자주 사용되는 형식 지우개 예시
1. AnyLayout
AnyLayout은 다양한 Layout 유형(VStack, HStack, ZStack 등)을 추상화하여 동적으로 레이아웃을 전환하거나 관리할 수 있도록 돕는 형식 지우개입니다. SwiftUI의 새로운 Layout 프로토콜과 함께 사용됩니다.
import SwiftUI
struct AnyLayoutExample: View {
@State private var isVertical = true
var body: some View {
let layout = isVertical ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
return layout {
ForEach(0..<3) { index in
Text("Item \(index)")
.padding()
.background(Color.blue)
.cornerRadius(8)
}
}
.animation(.default, value: isVertical)
.onTapGesture {
isVertical.toggle()
}
}
}
주요 활용: 다양한 레이아웃 전환 및 조합이 가능. 레이아웃을 동적으로 변경해야 할 때 강력한 도구.
2. AnyPublisher (Combine)
Combine 프레임워크에서 사용하는 AnyPublisher 또한 타입 지우개의 한 종류입니다. AnyPublisher는 여러 타입의 Publisher를 단일 타입으로 추상화합니다. 예를 들어, Just, Future, PassthroughSubject 등 다양한 퍼블리셔를 하나로 처리할 수 있습니다.
import Combine
func createPublisher() -> AnyPublisher<String, Never> {
if Bool.random() {
return Just("Hello").eraseToAnyPublisher()
} else {
return Future<String, Never> { promise in
promise(.success("World"))
}
.eraseToAnyPublisher()
}
}
주요 활용: 비동기 데이터 스트림 처리에서 유연성 확보.
3. AnyHashable
AnyHashable는 다양한 Hashable 타입을 하나의 컬렉션에 담을 수 있도록 합니다. 예를 들어, String, Int, UUID 등의 해시 가능한 값을 같은 배열이나 딕셔너리에 저장할 수 있습니다.
let hashables: [AnyHashable] = [123, "Hello", UUID()]
print(hashables)
주요 활용: 다양한 해시 가능한 데이터를 통합 관리.
4. AnyShape
AnyShape는 여러 Shape를 추상화하여 하나의 타입으로 취급할 수 있도록 도와주는 형식 지우개입니다. 다양한 Shape(예: Circle, Rectangle, Capsule)을 하나의 AnyShape로 래핑해 사용하면, 동적 모양 변경과 같은 기능을 구현할 수 있습니다.
import SwiftUI
struct AnyShapeExample: View {
@State private var isCircle = true
var body: some View {
VStack {
if isCircle {
AnyShape(Circle())
} else {
AnyShape(Rectangle())
}
.fill(Color.blue)
.frame(width: 100, height: 100)
Button("Toggle Shape") {
isCircle.toggle()
}
}
}
}
주요 활용: 다양한 해시 가능한 데이터를 통합 관리. 단점으로는 각 모양의 고유한 프로퍼티를 사용할 수 없음.
형식 지우개의 추가 용법
1. 사용자 정의 형식 지우개
Swift에서는 직접 형식 지우개를 만들 수도 있습니다. 예를 들어, 아래와 같이 Animal 프로토콜을 형식 지우개로 추상화할 수 있습니다.
protocol Animal {
func sound() -> String
}
struct Dog: Animal {
func sound() -> String {
"Woof"
}
}
struct Cat: Animal {
func sound() -> String {
"Meow"
}
}
struct AnyAnimal: Animal {
private let _sound: () -> String
init<T: Animal>(_ animal: T) {
_sound = animal.sound
}
func sound() -> String {
_sound()
}
}
let animals: [AnyAnimal] = [AnyAnimal(Dog()), AnyAnimal(Cat())]
animals.forEach { print($0.sound()) }
장점: 다양한 타입을 추상화하면서 공통된 인터페이스 제공.
2. Closure를 활용한 형식 지우개
구조체와 클로저를 사용해 간단한 형식 지우개를 구현할 수도 있습니다.
struct AnyAction {
private let _perform: () -> Void
init<T>(_ action: T) where T: Action {
_perform = action.perform
}
func perform() {
_perform()
}
}
protocol Action {
func perform()
}
struct PrintAction: Action {
func perform() {
print("Action performed!")
}
}
let action = AnyAction(PrintAction())
action.perform()
형식 지우개 사용 시 주의사항
- 성능 비용: 타입 정보를 숨기고 동적으로 처리하기 때문에 런타임 비용이 약간 증가할 수 있습니다.
- 타입 안전성 감소: 타입 지우개를 지나치게 사용하면 Swift의 타입 시스템의 장점을 잃을 수 있습니다.
- 유지보수성: 내부 타입 정보가 감춰지기 때문에 디버깅이나 코드 추적이 어려워질 수 있습니다.
정리
형식 지우개는 다음과 같은 상황에서 유용합니다:
- 동적 타입 처리: 다양한 타입을 하나로 추상화.
- 유연한 데이터 처리: 동적으로 변경되거나 다양한 구성 요소를 포함하는 데이터 관리.
- 추상화와 다형성: 객체지향적 설계를 간소화.