AssociatedType

이원희·2021년 2월 21일
0

 🐧 Swift

목록 보기
23/32
post-thumbnail

오늘은 AssociatedType에 대해서 알아보자.
AssociatedType은 어떤 코드 보다 있길래 호기심에 알아봤는데 공식문서에서 Generics와 같은 파트에 있었다.
조만간 Generics도 정리해야지...

AssociatedType

AssociatedType이란 뭘까?

공식문서에 따르면 프로토콜 정의의 일부이다.
프로토콜을 정의 할 때 하나 이상의 연관 타입을 선언할때 유용하다.
오호.. 하나 이상의 연관 타입이라... 좀 더 알아보자.

AssociatedType은 프로토콜의 일부분으로 사용되는 타입에 placeholder 이름을 제공한다.
해당 AssociatedType에 사용할 실제 타입은 프로토콜이 채택 될 때까지 지정되지 않는다.

오... AssociatedType프로토콜에서 타입의 placeholder(임시?라고 할 수 있을거 같다? placeholder의 정확한 의미를 한국어로 못 찾겠다...)의 역할이고, 프로토콜이 채택될 때 실제 사용할 타입이 정해진다.
(뭔가 placeholder의 역할을 한다고 하니 Typealias가 생각났는데 원래는 Teypalias였는데 이름이 바뀌었다고 한다.ㅋㅋㅋ)


Associated Types in Action

예시를 통해 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은 프로토콜에서만 정의되고, 타입을 정의하거나 typealiasassociatedtype의 조건을 충족하라고 한다.

따라서 프로토콜을 채택한 곳에서는 typealias로 프로토콜에서 사용할 타입을 정의해주자!

typealias Item = String 구문을 통해 프로토콜에 선언한 associatedtype Item은 String 타입으로 정의된다.

그리고 사실 typealias Item = String 구문이 없어도 잘 돌아간다.
이것은... Swift의 타입 추론 덕분이다.


Adding Constraints to an Associated Type

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에 대해서 알아봤다.
처음에는 왜 이런걸 쓰지? 라는 호기심에 알아봤는데 생각보다 재밌었다.ㅋㅋㅋ
그리고 잘 쓰면 뭔가 멋진 코드가 나올거 같은데 나는 아직 안될거 같다.....ㅠ
언젠가 멋진 코드를 짜는 사람이 될때까지 공부를 열심히해야지..!
그럼 이만👋

2개의 댓글

comment-user-thumbnail
2021년 2월 24일

좋은글 잘봤습니다. 이렇게 정리되면 업무에 도움이 많이 되실까요? 궁금해서요~

1개의 답글