타입의 placeholder, Associated type에 대해서 알아보자.


Associated Types

Associated typetype에 대한 placeholder 이름을 주며, 프로토콜에서 사용됩니다. (placeholder는 임시로 자리를 차지하는 Character 또는 String 값) Associated type에 실제로 사용될 타입은 ptorocol이 채택될 때까지 명시되지 않습니다. Associated type은 코드상에서 associatedtype이라는 키워드로 표기됩니다.

placeholder

In computer programming, a placeholder is a character, word, or string of characters that may be used to take up space until such a time that the space is needed. For example, a programmer may know that she needs a certain number of values or variables, but doesn’t yet know what to input


Item이라는 associated type을 선언한 프로토콜 Conatiner를 예시로 보겠습니다.

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

Contianer 프로토콜에 있는 associatedtype 로 선언된 Item어떻게 저장되어야하는지 또는 어떠한 타입으로 사용되어야하는지 명시하지 않았습니다. 프로토콜은 3가지 기능을 명시했고, Container로 간주되기 위해서 Item으로 사용될 어떤 타입(any type)이 제공해야 합니다. 채택 타입(conforming type)은 이 3가지 요구사항이 충족되는 한, 추가적인 기능을 제공할 수 있습니다.

Container 프로토콜을 채택한 어떤 타입(any type)은 저장하는 값의 타입을 지정할 수 있어야합니다. 특히 적절한 타입의 item만이 container에 추가될 수 있고, subscript에 의해 리턴되는 item의 타입을 명확하게 해야합니다.

이러한 요구사항들을 정의하기 위해서, Container 프로토콜은 어떤 container에 사용될 타입이 무엇인지 알지 못하는 상태에서 container가 가지게 될 요소의 타입을 참조하는 방법이 필요합니다. Container 프로토콜은 append(_:) 메소드에 전달되는 어떤 값(any value)은 container의 요소 타입과 동일한 타입을 가져야한다는 것, 그리고 container의 subscript에 의해 리턴되는 값 또한 container의 요소 타입과 동일한 타입을 가져야한다는 것을 명시해야합니다.

이를 위해서 Container 프로토콜은 Item이라는 associated type을 정의하며, 코드상에서는 associatedtype Item 와 같이 표기됩니다. 프로토콜은 직접 Item이 무엇인지 정의하지 않으며 그 정보는 나중에 어떤 채택 타입(any conforming type)으로 제공하기 위해 남겨둡니다. Item 별칭(alias)은 Container의 item의 타입을 참조할 방법, 그리고 append(_:) 메소드와 subscript에 사용될 타입을 정의하는 방법을 제공하며, Container의 예상 동작이 시행되도록 한다.


아래 코드는 generic을 사용하지 않고 구현된 IntStack입니다.

struct IntStack: Container {
    // original IntStack implementation
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // conformance to the Container protocol
    typealias Item = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

IntStackContainer 구현에 사용할 적절한 ItemInt 타입이라고 명시했습니다.

typealias Item = Int

위 정의는 Container 프로토콜 정의에 있어 Item추상 타입(the abstract type)Int라는 하나의 구체적인 타입(a concrete type)으로 바꿉니다.

사실 Swift의 타입 추론 덕분에, 구체적으로 ItemInt타입이라고 IntStack의 정의 부분에 선언할 필요는 없습니다. IntStackContainer 프로토콜의 필수기능들을 전부를 채택하기 때문에, Swift는 append(_:) 메소드의 파라미터와 subscript의 리턴타입을 간단히 보는 것만으로도 사용하기 적절한 Item을 추론할 수 있습니다. 실제로, typealias Item = Int 코드라인을 지우게 되면, 모든게 정상적으로 작동하는데, 그 이유는 어떤 타입이 Item으로 사용될지 명확하기 때문입니다.


Summary

  • Protocol에서 명시되는 associated type은 타입을 명시하지 않습니다.
  • Associated type의 타입은 protocol을 채택한 클래스에서 선언됩니다.
  • Associated type 구현 방법은 두가지가 있습니다.
  • typealias를 사용한 방법과 protocol에 사용된 추상 타입(the abstract type)을 특정 타입으로 모두 구현하는 방법입니다.

위 언급한 associated type 구현 중 후자 방법은 구현 중 실수의 우려가 있습니다.


Extending an Existing Type to Specify an Associated Type

Adding Protocol Conformance with an Extension에 명시된 것 처럼, 채택(conformance)을 프로토콜에 추가하기 위해서 기존에 존재하는 타입을 확장시킬 수 있습니다. 이는 Associated type을 가지고 있는 protocol을 포함합니다.

Swift의 Array 타입은 이미 append(_:) 메소드, count 프로퍼티, 그리고 Int index로 요소를 가져오는 subscript를 제공합니다. 이 3가지 기능은 Container의 필수요건들과 일치합니다. 이는 Array가 프로토콜을 채택한다고 선언함으로써, Container 프로토콜에 맞게 Array를 확장할 수 있습니다. Declaring Protocol Adoption with an Extension에 설명 된대로 빈 extension으로 작업을 수행합니다.

extension Array: Container {}

Arrayappend(_:) 메소드와 subscript는 Swift가 Item에 사용할 적절한 타입을 추론할 수 있게 해줍니다. 이 extension을 정의한 후, 어떤 Array든지 Container처럼 사용할 수 있습니다.


Adding Constraints to an Associated Type

프로토콜의 associated type에는 ‘타입 제약(type constraints)’을 추가할 수 있습니다. Associated type에 들어오게 될 타입은 명시된 제약 조건을 충족해야합니다. 예를 들어, 아래 코드는 item은 container에서 Equatable을 채택한 타입만 사용되게 Container에서 정의되어있습니다. Equatable을 채택하지 않은 타입을 associated type에 지정하게 되면 컴파일 에러가 발생합니다.

protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

Container 버전을 채택하려면, container의 Item 타입은 Equatable 프로토콜을 채택해야힙니다.


The Original Document

댓글남기기