ProgrammingiOS 開発者, 中級/上級

Swiftにおけるopaque types (some)とは何ですか、それはなぜ必要で、protocol typesの代わりにいつ使用した方が良いですか?

Hintsage AIアシスタントで面接を突破

回答。

問題の歴史: 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型内の具体的な型は常に1つです
  • 使用場所ではassociatedtypeをサポートしておらず、定義ではサポートしています
  • 宣言型API(例:SwiftUI)の構築に使用されます

罠のある質問。

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を使用している場合は特にそうです。

一般的なエラーとアンチパターン

  • someを通じて異なる型を返そうとする(コンパイルエラー)
  • 状態を保持する必要がある場合にsomeを使用する(例えば、状態を記録するために)
  • 古いスタイルのprotocol typesを使い続けることで最適化を逃す

実生活の例

ネガティブケース

プロジェクト内で全てがany Protocolを通じて返され、コレクションが型安全性を失い、downcastでバグが発生し、コンパイル時の最適化が遅れます。

利点:

  • 異なる型を組み合わせる柔軟なアーキテクチャ

欠点:

  • 型安全性の喪失、効率の低下、キャストの問題

ポジティブケース

SwiftUIデザインでは、コンポーネントがsome Viewを返し、各モジュールが内部型を明確に定義します。バンドルサイズが縮小し、ビルドが高速化されます。

利点:

  • パフォーマンスの向上、型安全性の向上

欠点:

  • 動的な保持が必要な場合の柔軟性の喪失