제네릭은 특정 타입에 종속되지 않고 다양한 타입에 대해 타입 안정성을 유지하면서, 다양한 타입에 대해 유연하게 동작할 수 있게 해주는 문법.
등에 적용이 가능.
func swapTwoValue<T>(_ value1: inout T, _ value2: inout T) -> Void {
let temp = value1
value1 = value2
value2 = temp
}
var num1 = 0
var num2 = 1
swapTwoValue(&num1, &num2)
swapTwoValues<T>(_:_:)
는 제네릭 함수로, T
는 타입 매개변수.
T
는 특정 타입에 구애받지 않고 함수가 호출될 때, 구체적인 타입으로 결정된다.
💡 즉, 함수가 실제로 호출되는 시점에 전달된 인자의 타입에 맞게 자동으로 타입이 결정된다.
class Container<T> {
var items: [T] = []
func getItems() -> [T] {
return items
}
func appendItem(_ value: T) {
self.items.append(value)
}
}
let intContainer = Container<Int>()
intContainer.items = [10, 20, 30]
intContainer.appendItem(0)
print(intContainer.getItems())]
// Prints: [10, 20, 30, 0]
let stringContainer = Container<String>()
stringContainer.items = ["Hello"]
stringContainer.appendItem("World")
print(stringContainer.getItems())
// Prints: ["Hello", "World"]
Container<T>
는 타입 매개변수 T
를 사용하여 어떤 타입이든 담을 수 있는 클래스.
메서드에서 제네릭 타입 T
를 활용해 다양한 타입의 데이터를 처리할 수 있다.
protocol ContainerType {
associatedtype Item
var items: [Item] { get }
func getItems() -> [Item]
func appendItem(_ value: Item) -> Void
}
프로토콜 또한 제네릭화 할 수 있으며, 프로토콜 내부에서 associatedtype
키워드를 사용.
associatedtype
키워드를 통해 연관 타입을 정의하며, 연관 타입은 프로토콜을 채택하는 타입이 나중에 구체적으로 지정할 수 있는 타입 자리 표시자.
💡 즉,
associatedtype
: 프로토콜 안에서 사용할 타입을 미리 정하지 않고!, 프로토콜을 채택하는 쪽에서 나중에 정하게 하는 일종의 Placeholder.
class IntContainer: ContainerType {
typealias Item = Int
var items: [Item] = []
func getItems() -> [Item] {
return items
}
func appendItem(_ value: Item) -> Void {
self.items.append(value)
}
}
일반적으로 typealias
키워드를 사용하여 associatedtype
의 타입을 명시
let intContainer = IntContainer()
intContainer.items = [10, 20]
intContainer.appendItem(30)
print(intContainer.getItems())
// Prints: [10, 20, 30]
또는 아래와 같이 사용
class StringContainer: ContainerType {
var items: [String] = []
func getItems() -> [String] {
return items
}
func appendItem(_ value: String) -> Void {
self.items.append(value)
}
}
associatedtype
의 타입이 사용되는 곳에서 typealias
없이도 어떤 타입인지 충분히 추론이 가능한 경우, typealias
를 생략 가능.
💡 보통은 추론 가능하게끔 하고,
typealias
생략
let strContainer = StringContainer()
strContainer.items = ["Hello", "World"]
strContainer.appendItem("!")
print(strContainer.getItems())
// Prints: ["Hello", "World", "!"]
protocol ContainerType {
associatedtype Item
var items: [Item] { get }
var count: Int { get }
func getItem(at index: Int) -> Item
func getItems() -> [Item]
func appendItem(_ value: Item) -> Void
}
class Container<T>: ContainerType {
// typealias Item = T // 생략해도 무방
var items: [T] = []
var count: Int { return items.count }
func getItem(at index: Int) -> T {
return items[index]
}
func getItems() -> [T] {
return items
}
func appendItem(_ value: T) -> Void {
items.append(value)
}
}
let intContainer = Container<Int>()
intContainer.items = [10, 20]
print(intContainer.items.count)
// Prints: 2
print(intContainer.getItem(at: 1))
// Prints: 20
intContainer.appendItem(30)
print(intContainer.getItems())
// Prints: [10, 20, 30]
제네릭 사용으로 다양한 타입을 다룰 수 있다는 점에서는 공통점이 있으나, 접근 방식과 유연성의 수준에는 차이가 있음.
Container<T>
예제)적합한 상황:
예:
Container<Int>
,Container<String>
등 → 타입을 직접 지정해서 객체 생성
IntContainer: ContainerType
예제)associatedtype
을 사용하여 프로토콜 정의,IntContainer
/ StringContainer
)는 특정 타입(Int)에 고정됨적합한 상황:
예: 오직
Int
값과String
값만 다루는IntContainer
/StringContainer
Container<T>: ContainerType
예제)적합한 상황:
예:
Container<String>
,Container<Double>
등 → 타입 유연성과 구조적 설계 가능
방식 | 타입 유연성 | 프로토콜 사용 | 재사용성 | 복잡도 |
---|---|---|---|---|
class Container<T> | 높음 | ❌ | 높음 | 낮음 |
class IntContainer: ContainerType | 낮음 (Int 고정) | ✅ | 낮음 | 낮음 |
class Container<T>: ContainerType | 높음 | ✅ | 높음 | 중간 |
상황에 따라 다르지만, 확장성과 유연성이 중요한 경우 associatedtype
+ 제네릭
가장 강력.