Swift プロトコルの連想型(associatedtype)

1.プロトコルの連想型(associatedtype)の基本

プロトコルの基本的な定義では、

  • プロパティの型
  • メソッドの引数の型
  • メソッドの戻り値の型

上記のような型をプロトコルの定義で決定するのが普通ですが、プロトコル定義時ではなく、準拠する型(クラス・構造体など)の定義時に上記の型を決定する方法もあります。これがプロトコルの連想型(associatedtype)。

プロトコルの連想型(associatedtype)の定義方法

protocol プロトコル名{
    associatedtype 連想型名
    
    var プロパティ名:連想型名
    func メソッド名(引数名:連想型名)
    func メソッド名()->連想型名
}

プロトコル定義時に上記のように連想型を宣言し、そのプロトコルを準拠する型の定義で連想型の型を具体的に指定する、という感じですね。


連想型の指定方法

typealias 連想型名=指定する型名

sample1-1

protocol Container{  //(1)
    associatedtype SVT
}

protocol SomeData{  //(2)
    associatedtype ValueContainer:Container where ValueContainer.SVT:Equatable
}


struct IntContainer:Container{  //(3)
    typealias SVT=Int
    var value:SVT=0
}

struct StringContainer:Container{  //(4)
    typealias SVT=String
    var value:SVT="sample string"
}

let value1=IntContainer(value:10)  //(5)
print(value1.value)

let value2=StringContainer(value:"override string")  //(6)
print(value2.value)
10
override string

SVT=(Stored Value’s Type)

sample1-1の(1)

Containerプロトコル内で連想型SVTを宣言。

sample1-1の(3)

Containerプロトコルに準拠した構造体IntContainer型を定義。その定義内で連想型SVTをInt型と指定。

sample1-1の(4)

Containerプロトコルに準拠した構造体StringContainer型を定義。その定義内で連想型SVTをString型と指定。

上記が連想型の基本的な使い方です。


2.プロトコルの連想型(associatedtype)の型制約

準拠する型(クラス・構造体など)において、プロトコルの連想型を指定する方法を見てきましたが、指定する型の性質をプロトコル内で指定することができます。

「準拠先の型(クラス・構造体など)は、指定する連想型をこういう型にしなさいよ。」というのをプロトコルの定義内で示すわけですね。

示す方法は以下。

protocol プロトコル名{
    associatedtype 連想型名:プロトコル名 or スーパークラス名
}

上記の記述で「連想型に指定する型は「〇〇プロトコルに準拠した型」あるいは「△△クラスを継承したクラス」にしなさい」と指定することができます。

sample1-2

protocol Container{  //(1)
    associatedtype SVT
}

protocol Data{  //(2)
    associatedtype SDT:Container
}


struct IntContainer:Container{  //(3)
    typealias SVT=Int
    var value:SVT=0
}

struct StringContainer:Container{  //(4)
    typealias SVT=String
    var value:SVT="defaul string"
}

let value1=IntContainer(value:0)  //(5)
print(value1.value)
let value3=IntContainer(value:54)
let value4=StringContainer(value:"value4")


struct PointData1:Data{  //(6)
    typealias SDT=IntContainer
    var point:SDT=value1
}
var point1=PointData1(point:value3)
print("\(point1.point.value)点")



struct PointData2:Data{  //(7)
    typealias SDT=StringContainer
    var point:SDT=value4
}

var point2=PointData2(point:value4)
print("\(point2.point.value)点")

 

0
54点
value4点

sample1-2.(1)

Containerプロトコルを定義。連想型SVTを宣言していますので、Containerプロトコルに準拠する型(クラス・構造体など)は、型の定義内で

  • typealiasキーワードを使って連想型を具体的に指定
  • 実装の中で連想型を指定

などの方法で連想型を指定する必要があります。


sample1-2(3)

Containerプロトコルに準拠した構造体IntContainerを定義しています。定義内で

typealias SVT=Int

とすることで連想型SVTをInt型に指定しています。


sample1-2.(2)

Dataプロトコルを定義。連想型SDT(Stored Data’s Type)を宣言していますので、Dataプロトコルに準拠する型(クラス・構造体など)は、連想型SDTの型を指定する必要があります。(2)で

associatedtype SDT:Container

と宣言してますので、Dataプロトコルに準拠する型(sample1-2では構造体PointData)内で指定する連想型SDTの型は、「Containerプロトコルに準拠した型」である必要があります。


sample1-2(6)

Dataプロトコルに準拠した構造体PointData1型を定義しています。

typealias SDT=IntContainer

としていますので、連想型SDTに構造体IntContainer型を指定しています。sample1-2(3)を見ればわかり通り、IntContainer型はConainerプロトコルに準拠した型なので、(2)の型制約

associatedtype SDT:Container

を満たしています。

構造体PointData1型にはpointプロパティがあり、pointプロパティの型は連想型SDT、そしてSDTはIntContainer型と指定されていますので、結局pointプロパティにセットされるのはIntContainer型のインスタンスとなります。


sample1-2(7)

Dataプロトコルに準拠した構造体PointData2型を定義しています。

typealias SDT=StringContainer

で、連想型SDTに構造体StringContainer型を指定しています。

以下は(6)と同じです。sample1-2ではDataプロトコルの連想型SDTの制約として「Containerプロトコルに準拠した型であること」という制約のみですので、SDTにIntContainerを指定しても、StringContainerを指定してもどちらでも問題ありません。


3.where節を使ってさらに連想型を制約する条件を記述する

sample1-2ではDataプロトコルの連想型SDTを指定する際、「Containerプロトコルに準拠した型」を指定するよう制約していましたが、もっと具体的に細かく条件を指定することもできます。

sample1-3

protocol Container{  //(1)
    associatedtype SVT
}

protocol Data{  //(2)
    associatedtype SDT:Container where Self.SDT.SVT == Int
}


struct IntContainer:Container{  //(3)
    typealias SVT=Int
    var value:SVT=0
}

struct StringContainer:Container{  //(4)
    typealias SVT=String
    var value:SVT="defaul string"
}

let value1=IntContainer(value:0)  //(5)
print(value1.value)
let value3=IntContainer(value:97)
let value4=StringContainer(value:"value4")


struct PointData:Data{  //(6)
    typealias SDT=IntContainer
    var point:SDT=value1
}
var point1=PointData(point:value3)
print("\(point1.point.value)点")


/*
struct PointData:Data{  //(7)エラー
    typealias SDT=StringContainer
    var point:SDT=value4
}

var point1=PointData(point:value4)
print("\(point1.point.value)点")
*/
0
97点

sample1-3ではDataプロトコル内の、連想型SDTの型制約の条件記述が、

associatedtype SDT:Container where Self.SDT.SVT == Int

のようになっています。Selfは自分(Dataプロトコル)に準拠した型(sample1-3ではPointData構造体)を指します。この記述で、

「連想型SDTは「Containerプロトコルに準拠した型」である必要があり、さらにSDTに指定した型(IntContainer型)内で指定される連想型SVTがInt型と指定されている必要がある」

という条件になります。

条件の前半部分

associatedtype SDT:Container

より連想型SDTはContainerプロトコルに準拠した型である必要があります。Containerプロトコルに準拠した型はその型の定義内で連想型SVTの型を指定する必要があります。

そのSVTがInt型に指定されている型(sample1-3(6)ではIntContainer)である必要がありますよ、という制約を記述しているわけですね。

 

試しに(6)部分をコメントアウトして、さらに(7)部分のコメントアウトを外して実行してみるとコンパイルエラーとなります。

(7)では連想型をStringContainer型に指定した構造体PointDataを定義しています。

SDTに指定されたStringContainer型の定義内で指定されている連想型SVTはString型です。(4)

Dataプロトコルに準拠した構造体PointData内で、連想型SDTをStringContainerに指定してしまうと、Dataプロトコルの連想型の制約

associatedtype SDT:Container where Self.SDT.SVT == Int

を満たせていない状態になりますので、コンパイルエラーが出る、ということですね。

コメントを残す

メールアドレスが公開されることはありません。