🐕

Swift some と any について

2024/07/12に公開

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