안녕하세요 릴리입니다😊
오늘은 General한 함수와 타입을 만들 수 있는 방법인 Generic과 associatedtype에 대해 공부해보겠습니다.
📙Swift Language Guide - Generic을 토대로 작성되었습니다.
함수나 타입에서 구체적인 타입 대신 플레이스 홀더 타입을 사용하여, 특정한 타입에 국한되지 않는 general한 함수와 타입을 만들 수 있는 문법입니다.
스위프트 스탠다드 라이브러리의 Dictionary, Array도 제네릭으로 만들어진 타입입니다.
구체적인 타입 대신 타입 파라미터라는 것을 사용합니다. 제네릭을 사용하려는 함수나 타입의 이름 뒤 꺽쇠(<>
)안에 파라미터 타입을 작성해줍니다.
타입 파라미터는 함수가 호출될 때, 타입이 인스턴스화 될 때 구체적인 타입으로 결정됩니다.
타입 파라미터에 의미가 있는 경우 이름을 지어줄 수도 있습니다.
(Dictionary<Key,Value>
와 같이)
관계가 없는 경우엔 보통 T
, U
, V
를 사용합니다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
동일한 기능의 함수나 타입이지만, 변수, 파라미터, 리턴값의 타입만 다르게 구현해야하는 경우에 제네릭을 사용하면 한가지 정의로 다양한 구체 타입을 커버할 수 있습니다.
파라미터로 전달 받는 a와 b를 swap하는 함수를 만들어본다고 합시다.
제네릭을 사용하지 않고 String, Double, Int 타입의 a, b를 swap하는 함수를 만들려고 하면 각 타입마다 함수를 만들어줘야합니다. 즉, 총 3개의 함수가 필요하게 됩니다.
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoInt(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
하지만 제네릭을 사용하면 하나의 함수면 충분합니다.. 단지, 함수를 호출할 때 원하는 타입을 넣어주기만 하면 됩니다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
// Int를 swap하는 경우
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
// String을 swap하는 경우
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
제네릭에서 타입 파라미터안에는 어떤 타입이든 들어올 수 있습니다.
하지만 Type constraints를 사용하면 특정한 프로토콜을 채택하거나 특정한 클래스를 상속하는 타입만 들어올 수 있도록 강제할 수 있습니다.
타입 파라미터의 콜론(:
)뒤에 요구되는 프로토콜이나 타입을 작성합니다.
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
여러개의 프로토콜을 채택하도록 아래와 같이 제한할 수도 있습니다.
protocol SomeProtocol {
}
protocol AnotherProtocol {
}
func read<T: SomeProtocol & AnotherProtocol>(book: T) {
}
프로토콜 내에서 사용되는 플레이스 홀더 타입을 associated type이라고 합니다.
프로토콜을 제네릭하게 사용할 수 있는 방법입니다.
protocol Container {
associatedtype Item
var element: Item { get }
mutating func append(_ item: Item)
}
이 associatedtype은 프로토콜이 구현될 때 typealias
로 구체 타입으로 결정할 수 있습니다.
typealias
로 지정struct Bag: Container {
typealias Item = String
var element: String
mutating func append(_ item: String) {
//
}
}
associatedtype
이 사용된 자리에 타입 파라미터를 넣어줌struct Box<T>: Container {
var element: T
mutating func append(_ item: T){
//
}
}
associatedtype도 제네릭과 동일하게 type constraints를 줄 수 있습니다.
protocol Container {
associatedtype Item: Equatable
var element: Item { get }
mutating func append(_ item: Item)
}
Type constraints, 타입 제약은 특정한 프로토콜이나 클래스에 대한 제약만 가능합니다.
하지만 where절은 특정한 프로토콜, 클래스 제약이외에도 다양한 조건을 추가할 수 있습니다.
먼저 특정한 프로토콜 채택 제약, 클래스 상속 제약에 대한 작동은 타입 제약과 where절이 동일합니다.
// 타입 제약
struct Box<T: Equatable>: Container {
var element: T
mutating func append(_ item: T){
//
}
}
// where절
struct Box<T>: Container where T: Equatable {
var element: T
mutating func append(_ item: T){
//
}
}
하지만 where절만 associatedtype과 관련된 제약등 더 다양한 제약을 추가할 수 있습니다.
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
// Check that both containers contain the same number of items.
if someContainer.count != anotherContainer.count {
return false
}
// Check each pair of items to see if they're equivalent.
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// All items match, so return true.
return true
}
associatedtype은 프로토콜을 제네릭하게 사용할 수 있는 방법이라 추상화에 유용하게 사용될 수 있는데요. 이런 프로토콜을 어떠한 변수에 대한 타입이나, 리턴 타입, 파라미터 타입으로서는 사용이 불가능합니다.😰
사용하려고 하면 아래와 같은 경고문을 만나게 됩니다.
제네릭 제약으로서만 사용이 가능하다고 합니다.
https://github.com/apple/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md
Swift 5.7 부터는 associatedtype과 관련된 사항에 변동이 있는 것 같은데,
타입으로 사용이 가능한지 추후에 읽어보아야겠습니다.