Swift の変数で型を扱う
Swift プログラミング
Swift の変数を型でやり取りしたいときには Type 型を使用します。
ここではその特徴と、型の判定方法について見ていきます。
変数で型を扱う
Swift では Any.Type型 を使うと、型そのものを変数に入れて扱えます。
型そのものは、Swift では「型名.self」で取得できます。そしてそれを Any.Type型 の変数に入れて持ち回ることができるようになっています。
let type:Any.Type = MyStruct.self
Any.Type の特徴
Any.Type型 には型そのものだけを代入できます。
型そのものは、他にも Any型 へ代入することもできますが、こちらを使った場合には、型だけに限らずインスタンスも扱えます。
let any1:Any = MyStruct.self
let any2:Any = MyStruct()
そのため、厳密に「型」を扱いたい場合には、扱う変数の型を Any.Type型 にすると良いでしょう。
インスタンスから型を取得するには
実行時にインスタンスから型を取得したいときには dynamicType
を参照します。
let value = MyStruct()
let type = value.dynamicType
たとえばこのようにすることで、インスタンスとして生成済みの MyStruct型 から型を、プログラムの実行時に取得することができます。
扱える型を制限する
これまでの Any.Type型 ではどんな型でも扱えましたが、これをたとえば MyProtocol.Type型 で扱うことで、MyProtocolプロトコル に準拠している型だけに制限することも可能です。
たとえば MyProtocol に準拠した構造体があったとします。
struct MyStruct : MyProtocol
このとき、次のように MyStruct の型は MyProtocol.Type 型で扱えます。
struct MyStruct : MyProtocol
let value = MyStruct()
let type:MyProtocol.Type = value.dynamicType
MyProtocol に準拠しない型は MyProtocol.Type 型では扱えないため、以下のように代入しようとするとエラーになります。
let value = String()
let type:MyProtocol.Type = value.dynamicType
型を判定する
変数に代入した「型」は is演算子 を使って互換性を判定できます。
let type:Any.Type = value.dynamicType
if type is MyStruct.Type {
}
このように、変数value
に型が格納されている場合は、型を意味する MyStruct.Type
などと比較することで、その型と互換性があるかどうかを判定できます。
インスタンスの型を判定する場合
変数に格納されているのが型ではなくインスタンスだった場合は、ここで Type
を使わなくても次のように判定できます。
let value:Any = MyStruct()
if value is MyStruct {
}
インスタンスを持ち回している場合はこの方法を使うのが簡単でしょう。
プロトコルの準拠判定も可能
この is演算子 では、プロトコルの準拠判定も行えます。
let type:Any.Type = value.dynamicType
if type is MyProtocol.Type {
}
このようにすることで、型が MyProtocolプロトコル と互換性があるかを判定できます。プロトコルの準拠だけでなく、クラスの継承も加味した判定が可能です。
変数に格納した型同士で判定するには
これまでの調子で、変数に入れた型同士での一致性を比較しようと次のようにしたところ、is演算子 のところでビルドエラーになりました。
func isMatch(type1:Any.Type, type2:Any.Type) -> Bool {
return type1 is type2
}
'type2' is not a type
どうやら is演算子 の右辺に変数を記載することはできないようです。
何か別の方法があるのか探してみたのですが、今のところは is演算子 の代わりになるようなものは見つけられていません。
ただ、完全一致の判定でよければ ObjectIdentifier型 を使うと比較判定できそうです。
これを使うと、クラスインスタンスやメタタイプの一意の識別子を取得できるそうなので、型を格納した変数をこの変換イニシャライザに渡してあげれば、その型の識別子が得られます。識別子が具体的にどのような値になっているかは隠蔽されていて確認できない様子です。
let identfier1 = ObjectIdentifier(type1)
let identfier2 = ObjectIdentifier(type2)
そして ObjectIdentifier型 を等価演算子で比較することで、ふたつの型が完全に一致しているかを判定できます。この方法はあくまでも「型を表す識別子」を比較しているだけなので、型が持つプロトコルや継承関係は考慮されないことに注意です。
if identifier1 == identifier2 {
}
is演算子 のように継承関係などを考慮して判定できたら融通が利いて便利そうだったんですけど、とりあえず今のところその方法がわからないので、型情報を変数で運用したいときには完全一致判定で処理できるように設計するか、クラス継承を活用するなど、なんらかの工夫が必要になりそうです。
変数に入れた型からインスタンスを作るには?
さて、変数に型が入っているということは、そこからインスタンスを作れそうな気がします。
ただ、変数type で受け取った型のイニシャライザを実行する コード を記載したところ、ビルドの時点で "Segmentation fault 11" エラーで Swift コンパイラが強制終了してしまいました。
ローカルスコープ内だとうまく生成できたりすることもあって、コンパイラの不具合なのか、それともそもそもの言語構文からして間違いなのかはまだ判らないですけど、ひとまずは変数に入れた型からインスタンスを生成するのは避けたほうが安全かもしれませんね。
もし変数に入れた型同士で is演算子 相当の処理をしたり、インスタンスを作り出す方法を知っている方がいらっしゃいましたらぜひ教えてください。