오늘은 AssociatedType에 대해서 알아보자.
AssociatedType은 어떤 코드 보다 있길래 호기심에 알아봤는데 공식문서에서 Generics와 같은 파트에 있었다.
조만간 Generics도 정리해야지...
AssociatedType
이란 뭘까?
공식문서에 따르면 프로토콜 정의의 일부이다.
프로토콜을 정의 할 때 하나 이상의 연관 타입을 선언할때 유용하다.
오호.. 하나 이상의 연관 타입이라... 좀 더 알아보자.
AssociatedType
은 프로토콜의 일부분으로 사용되는 타입에 placeholder 이름을 제공한다.
해당 AssociatedType
에 사용할 실제 타입은 프로토콜이 채택 될 때까지 지정되지 않는다.
오... AssociatedType
은 프로토콜에서 타입의 placeholder(임시?라고 할 수 있을거 같다? placeholder의 정확한 의미를 한국어로 못 찾겠다...)의 역할이고, 프로토콜이 채택될 때 실제 사용할 타입이 정해진다.
(뭔가 placeholder의 역할을 한다고 하니 Typealias가 생각났는데 원래는 Teypalias였는데 이름이 바뀌었다고 한다.ㅋㅋㅋ)
예시를 통해 AssociatedType
이 어떻게 동작하는지 확인해보자.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
Container
라는 프로토콜을 정의했다.
associatedtype
을 Item으로 정의했다.
protocol에서는 구현부를 정의하지 않으니 associatedtype
또한 구현부를 정의하지 않는다.
프로토콜에 정의되어 있는 append()
와 subscdript
를 보면 Item을 사용하고 있다.
Container
프로토콜을 채택하는 struct를 구성해보자.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
struct IntStack: Container {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
struct StringStack: Container {
var items = [String]()
mutating func push(_ item: String) {
items.append(item)
}
mutating func pop() -> String {
return items.removeLast()
}
typealias Item = String
mutating func append(_ item: String) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> String {
return items[i]
}
}
Int
타입을 넣는 IntStack과 String
타입을 넣는 StringStack을 제네릭을 사용하지 않고 구현했다.
Stack with Swift에서 다뤘듯 보통 Swift로 Stack을 구현할 때에는 다양한 타입에 대응할 수 있도록 Generics
을 많이 사용한다.
그렇다면 위의 코드는 어떻게 Generics
를 사용하지 않고 다양한 타입에 대응할 수 있을까?
다 알겠지만 associatedType
을 사용한다.
Container
프로토콜에 associatedtype Item
을 선언했었다.
associatedtype
은 프로토콜이 채택될때 상세 타입이 정해지고, 타입의 placeholder 역할을 한다고 했다.
또한, 프로토콜은 채택한 곳에서 구현부를 정의힌다.
typealias Item = String
프로토콜을 채택한 곳에서 구현부를 정의하므로 해당 코드는 Container
프로토콜에 정의해 둔 associatedtype Item
의 구현부이다.
처음 봤을때 들었던 생각은 프로토콜을 채택하고 준수하려면 프로토콜에 정의되어 있는 형태를 그대로 가져와서 구현부를 정의해야 하는데 왜 프로토콜에서는 associatedtype
이고 프로토콜을 채택한 곳에서는 typealias
를 사용할까였다.
프로토콜을 채택한 곳에서 typealias
-> associatedtype
으로 수정하면 어떤 일이 일어날까?
이런 에러를 마주하게 된다.
associatedtype
은 프로토콜에서만 정의되고, 타입을 정의하거나 typealias
로 associatedtype
의 조건을 충족하라고 한다.
따라서 프로토콜을 채택한 곳에서는 typealias
로 프로토콜에서 사용할 타입을 정의해주자!
typealias Item = String
구문을 통해 프로토콜에 선언한 associatedtype Item
은 String 타입으로 정의된다.
그리고 사실 typealias Item = String
구문이 없어도 잘 돌아간다.
이것은... Swift의 타입 추론 덕분이다.
associatedtype
에 제약을 걸어줄 수도 있다.
(Generics
에서 제약을 걸 수 있듯이!)
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
class TestItem {}
struct TestStack: Container {
var items = [TestItem]()
mutating func push(_ item: TestItem) {
items.append(item)
}
mutating func pop() -> TestItem {
return items.removeLast()
}
typealias Item = TestItem
mutating func append(_ item: TestItem) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> TestItem {
return items[i]
}
}
Item에 Equatable
로 제약을 걸었다.
TestItem이 Equatable
을 준수하지 않아 에러가 나온다.
associatedtype에 대해서 알아봤다.
처음에는 왜 이런걸 쓰지? 라는 호기심에 알아봤는데 생각보다 재밌었다.ㅋㅋㅋ
그리고 잘 쓰면 뭔가 멋진 코드가 나올거 같은데 나는 아직 안될거 같다.....ㅠ
언젠가 멋진 코드를 짜는 사람이 될때까지 공부를 열심히해야지..!
그럼 이만👋
좋은글 잘봤습니다. 이렇게 정리되면 업무에 도움이 많이 되실까요? 궁금해서요~