오늘은 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에 대해서 알아봤다.
처음에는 왜 이런걸 쓰지? 라는 호기심에 알아봤는데 생각보다 재밌었다.ㅋㅋㅋ
그리고 잘 쓰면 뭔가 멋진 코드가 나올거 같은데 나는 아직 안될거 같다.....ㅠ
언젠가 멋진 코드를 짜는 사람이 될때까지 공부를 열심히해야지..!
그럼 이만👋
좋은글 잘봤습니다. 이렇게 정리되면 업무에 도움이 많이 되실까요? 궁금해서요~