プロトコルを定義するとき、プロトコルの定義の一部に関連型を宣言すると有益な場合があります。関連型 (associated types) は、プロトコルの一部として使用される型に、プレースホルダ名を与えます。その関連型に使用する実際の型は、プロトコルが採用されるまで指定されません。関連型は associatedtype キーワードで指定します。

関連型の動作

次は、関連型 ItemType を宣言するプロトコル Container の例です。

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

Container プロトコルは、あらゆるコンテナが提供する必要がある 3 つの必須性能を定義しています。

  • append(_:) メソッドでコンテナに新しいアイテムを追加できること
  • Int 値を返す count プロパティでコンテナにあるアイテム数にアクセスできること
  • Int インデックス値を取るサブスクリプトでコンテナの各アイテムを取り出せること

このプロトコルには、コンテナのアイテムがどのように保管され、どのような型が許容されるかを指定していません。プロトコルは、型が Container とみなされるために提供する必要がある 3 つの機能を指定しているだけです。3 つの要件を満たす限り、準拠する型に追加の機能を提供させることができます。

Container プロトコルに準拠する型は、保管する値の型を指定できる必要があります。特に、コンテナに適した型のアイテムのみ追加されるようにし、サブスクリプトで返されるアイテムの型について明確にする必要があります。

これらの要件を定義するためには、Container プロトコルに、特定のコンテナの型を知ることなく、コンテナが保持する要素の型を参照するための手段が必要です。append(_:) メソッドに渡される値がコンテナの要素の型と同じ型であり、コンテナのサブスクリプトで返される値がコンテナの要素の型と同じ型であることを、Container プロトコルに指定する必要があります。

これを達成するために、associatedtype ItemType として記述する関連型 ItemType を Container プロトコルに宣言します。プロトコルには ItemType が何であるかを定義せず、準拠する型がその情報を提供するようにします。それにもかかわらず、Container に期待される振る舞いが強制されるように、Container にあるアイテムの型を参照し、append(_:) メソッドとサブスクリプトで使用する型を定義する手段を ItemType エイリアスが提供します。

次はジェネリックでないバージョンの IntStack 型で、Container プロトコルに準拠するようにしています。

struct IntStack: Container {
    // IntStack のオリジナル実装
    var items = [Int]()
    mutating func push(item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // Container プロトコルに準拠
    typealias ItemType = Int
    mutating func append(item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

IntStack 型は Container プロトコルの 3 つの要件をすべて実装し、要件を満たすために各ケース内で IntStack 型の既存の機能の一部をラップしています。

さらに、Container のこの実装で使用する適切な ItemType は、Int 型であると指定しています。Container プロトコルのこの実装では、typealias ItemType = Int の定義で抽象的な ItemType 型を、具体的な Int 型に変えています。

Swift の型推論のおかげで、実際には IntStack の定義の一部として ItemType の具体的な Int を宣言する必要がありません。IntStack が Container プロトコルの要件すべてに準拠しているため、単に append(_:) メソッドの item パラメータの型とサブスクリプトの戻り値の型から、Swift は使用する適切な ItemType を推論することができます。実際、上のコードから typealias ItemType = Int の行を削除した場合でも、ItemType に使用される型が明確であるため、依然としてすべて動作します。

また、Container プロトコルに準拠するジェネリックな Stack 型にすることもできます。

struct Stack<Element>: Container {
    // Stack<Element> のオリジナル実装
    var items = [Element]()
    mutating func push(item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // Container プロトコルに準拠
    mutating func append(item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

今回は、型パラメータ Element が append(_:) メソッドの item パラメータの型、およびサブスクリプトの戻り値の型として使用されています。そのため、この特定のコンテナの ItemType として使用する適切な型を、Swift が推論することができます。

関連型を指定するために既存の型を拡張

Adding Protocol Conformance with an Extension で説明されているように、プロトコル準拠を追加するために既存の型を拡張することができます。これには、関連型のプロトコルが含まれます。

Swift の Array 型は、すでに append(_:) メソッドや count プロパティ、Int インデックスで要素を取り出すサブスクリプトを提供しています。これら 3 つの性能は、Container プロトコルの要件にマッチします。つまり、Array がプロトコルを採用すると宣言するだけで、Container プロトコルに準拠するよう Array を拡張することができます。Declaring Protocol Adoption with an Extension で説明されているように、空のエクステンションを記述します。

extension Array: Container {}

配列の既存の append(_:) メソッドとサブスクリプトが、上で見たジェネリックな Stack 型と同じように、ItemType に使用する適切な型を Swift が推論できるようにします。このエクステンションの定義後、あらゆる Array を Container として使用することができます。


Portions of this page are translations based on work created and shared by Apple and used according to terms described in the Creative Commons Attribution 4.0 International License.