Swift some と any について
some
some A
は Opaque Type と呼ばれる。
ジェネリクス、リバースジェネリクスのシンタックスシュガー。
ジェネリクス
以下2つは同義
func foo<A: Animal>(_ animal: A) {}
func foo(_ animal: some Animal) {}
こちらに記載してある「制約として」プロトコルを使用したい場合はsome
を使うのが良い。
リバースジェネリクス
ジェネリクスとは逆で、外部では具体的な型を隠蔽し、内部で具体的な型を実装する。
以下は同義
// リバースジェネリクス (あくまで概念でコンパイルは通らない)
func foo() -> <A: Animal> A {
return Dog()
}
func foo() -> some Animal {
return Dog()
}
具体的な型を隠蔽できる&パフォーマンスが向上するため、SwiftUIでも利用されている。
struct ContentView: View {
var body: some View {
...
}
}
パフォーマンス
existential containerが必要なく、オーバーヘッドが少ない。
また、コンパイル時に型が決定しているため、静的ディスパッチになり、オーバーヘッドが少ない。
func foo(_ animal: Animal) {}
このように「型として」プロトコルを扱う場合と比べてパフォーマンスが向上する。詳細はこちら
any
any A
は existential type と呼ばれる。
以下は同義。型としてのプロトコルを明示的に表す。
func foo(_ animal: Animal) {}
func foo(_ animal: any Animal) {}
パフォーマンス
existential containerを利用するため、メモリを余分に消費し、containerに包んだり、取り出したりするオーバーヘッドが発生する。
また、コンパイル時に型が決定していないため、動的ディスパッチになり、オーバーヘッドが発生。
someとanyの使い分け
someを利用する場合
func foo(_ animals: [some Animal]) {}
DogやCatを混在させたArrayを渡すことはできない。
struct Dog: Animal {}
struct Cat: Animal {}
foo([Dog(), Cat()]) // コンパイルエラー
これは、ジェネリック関数の型パラメータAが具体的な型1つを表すものであるため。
そのため以下のような配列は引数として渡すことが可能。
foo([Dog(), Dog()])
foo([Cat(), Cat()])
anyを利用する場合
func foo(_ animals: [any Animal]) {}
fooの引数には、DogやCatインスタンスを混在させることが可能。
foo([Dog(), Cat()])
基本パフォーマンス面で優れているsome
を使うと良いが、someとanyで出来ることが違うため、適切な使い分けが必要。
まとめ
someはジェネリクス、リバースジェネリクスのシンタックスシュガー。
anyは型としてのプロトコルを明示的に表すためのもの。
可能な限り、パフォーマンス面で優れているsome
を使うと良い。
Discussion