問題の歴史:
Opaque types (some)はSwift 5.1で登場し、関数やプロパティの返り値の型がコンパイラには知られているが、ユーザーには隠されている新しい抽象化の方法を提供しました。これはprotocol existentials (any Protocol)の代替手段ですが、関数内の特定の型に厳密に結びついています。
問題: 関数がassociatedtypeを持つプロトコルを返す場合(例:Sequence)、次のように直接書くことはできません:
func makeNumberSequence() -> Sequence { ... } // エラー
Protocol existentialsは任意の実装を返すことを許可しますが、毎回同じ型を保証するわけではありません:
func foo() -> any View { ... }
これは予測不可能性と弱い型安全性をもたらします。
解決策: Opaque result typeを使用する:
func makeNumbers() -> some Sequence { [1, 2, 3] }
これにより、コンパイラは実際の返り値の型を正確に知っていますが、それは外部からは隠されています。これにより、パフォーマンスの最適化が可能になり、安全性が向上し、SwiftUI DSLを使用でき、モジュール間の型の交換が容易になります。
主な特徴:
opaque typesは値を保持するために使用できますか(クラスのプロパティとして)?
いいえ。Opaque typesは関数の返り値またはcomputed propertiesのみに適用されます。値や値の配列を保持するためにはexistentials (any Protocol)を使用します。
同じ関数の異なる分岐がsomeで異なる型を返すことはできますか?
いいえ。コンパイラは、両方(またはすべて)の分岐が同じ具体的な型を返すことを要求します。そうでない場合、エラーが発生します:
func foo(flag: Bool) -> some Sequence { if flag { return [1, 2, 3] } else { return ["a", "b", "c"] // エラー } }
someを使用して複数の関数間で返り値の型を特定することは可能ですか?
いいえ。各someを持つ関数は、自身のユニークな隠された型を返します。たとえそれが実際には同じ配列であっても、ある関数の結果を別の関数の引数として使用することはできません。どちらも異なるプロトコルや異なる隠された型でsomeを使用している場合は特にそうです。
プロジェクト内で全てがany Protocolを通じて返され、コレクションが型安全性を失い、downcastでバグが発生し、コンパイル時の最適化が遅れます。
利点:
欠点:
SwiftUIデザインでは、コンポーネントがsome Viewを返し、各モジュールが内部型を明確に定義します。バンドルサイズが縮小し、ビルドが高速化されます。
利点:
欠点: