Opaque Type(不透明な型)は、関数がコール側に具体的な型を明かさずにある型の値を戻すことを許すSwiftのメカニズムです。戻り値の型におけるsomeキーワードは最も有名な例です: SwiftUIでのsome Viewは「関数がViewに準拠するいくつかの型を戻すが、どの型かは実装詳細だ」という意味です。Opaque typeは型のアイデンティティを保持します(型としてのプロトコルとは異なり)。これにより、コンパイラがコードを最適化し、戻される型の一貫性を保証できます。Swift Book, 2025によると、opaque typesは関連型を持つプロトコルの問題を解決し、そのようなプロトコルの値を関数から戻すことを可能にします。
ポイント
Opaque Typeはsomeキーワードで宣言される戻り値の型で、コール側から具体的な実装を隠します。コール側は、戻された値が特定のプロトコルに準拠していることしか知らず、someの背後にどの型があるのかは知りません。その間、コンパイラは正確な型を知っており、静的ディスパッチと最適化に使用します。
Swift 5.1 (SE-0244)でopaque typesが導入される前は、ボックスラッパーなしで関数から関連型を持つプロトコルを戻すことが不可能でした。例えば、Equatableプロトコルには関連型があり、関数は単にEquatableを戻すことができませんでした — コンパイラは「プロトコルはジェネリック制約としてのみ使用できます」というエラーを出しました。Opaque typeがこの問題を解決しました。
func makeInt() -> some Equatable {
return 42
}
func makeString() -> some Equatable {
return "Hello"
}
// コンパイラはmakeIntがIntを戻すことを知っている
// makeInt() == makeString() — ❌ エラー、異なる型
両方の関数はsome Equatableを戻しますが、具体的な型は異なります: IntとStringです。これらを==で比較しようとするとコンパイルエラーが発生します。なぜなら、opaque typeは特定の呼出しが同じ型を戻すことを保証しますが、異なる関数間では保証しないからです。これは機能であってバグではありません: opaque typeは、型としてのプロトコル(any Equatable)が失う型のアイデンティティを保持します。
GenericとOpaque Typeは表裡一体です。ジェネリックはコール側が型を選択でき、opaque typeは関数がコール側から型を隠すことができます。違いは制御の方向にあります。
| 特徴 | Generic | Opaque some |
|---|---|---|
| 型を誰が選ぶか | コール側のコード | 関数/メソッド |
| 型のアイデンティティ | 保持される(安定) | 保持される(安定) |
| 戻り値のブランチ数 | 一つ(ジェネリックにより) | すべてのブランチで同じ型 |
| 使用例 | アルゴリズム、データ構造 | SwiftUI、ファクトリメソッド |
ジェネリック関数では、コーラーが使用する型を決定します。関数は制約を満たすどのようなTでも動作する必要があります。Opaque typeでは、コーラーは具体的な型を知らず、実装が決定します。
// Generic: コーラーが型を選択する
func identity<T>(_ value: T) -> T { value }
let x: Int = identity(42)
// Opaque: 関数が型を隠す
func makeSomeEquatable() -> some Equatable { 42 }
let y = makeSomeEquatable()
ジェネリックとopaque typeの選択は意図によります。コール側が型を選ぶ必要があればジェネリックを使用します。関数が実装詳細を隠す必要があればsomeを使用します。SwiftUIがsome Viewを選んだのは、bodyが内部的に柔軟であるが外部的に安定する必要があったからです。
someはSwift 5.1 (SE-0244)で導入されたSwiftのキーワードです。戻り値の位置でopaque typeを宣言するために使用され、またパラメータ(SE-0341)やプロパティでも使用できます。someは、具体的な型が安定しておりコンパイラに知られているが、外部のコードからは隠されていることを保証します。
Swift 5.7から、someは戻り値の位置だけでなく、パラメータでも使用できます。パラメータでのsome Equatableは「この関数はどのようなEquatable型でも受け付けるが、特定のボディ内のすべての呼出しは同じ型を見る」という意味です。
func areEqual(_ a: some Equatable, _ b: some Equatable) -> Bool {
// aとb — 異なる可能性のある型、==は直接動作しない
return isEqual(a, b)
}
func isEqual<T: Equatable>(_ a: T, _ b: T) -> Bool {
return a == b
}
パラメータでsomeを使用すると、
anyはSwift 5.6+のキーワードで、存在的型(型としてのプロトコル)を明示的に宣言するためのものです。someとは異なり、anyは型のアイデンティティを消します: コンパイラはプロトコルの背後にどの具体的な型が隠れているかを知りません。これは柔軟性(同じ配列に異なる型を格納できる)を提供しますが、パフォーマンスの代償です。
some — 静的ポリモーフィズム: コンパイラは具体的な型を知り、直接ディスパッチを使用し、コードをインラインできます。any — 動的ポリモーフィズム: 仮想メソッドテーブル(存在的コンテナ)が使用され、間接性が増します。
protocol Drawable {
func draw()
}
// some: 静的な型が知られている
func makeDrawable() -> some Drawable {
return Circle() // 単一の戻り値の型
}
// any: 動的、異なる型を格納できる
var shapes: [any Drawable] = [Circle(), Square()]
shapes.append(Triangle())
someとanyの選択はパフォーマンスと柔軟性のトレードオフです。Someはより高速ですが単一の実装に制限されます。Anyはより柔軟(型を混合できる)ですが、動的ディスパッチにより遅くなります。SwiftUIでは、bodyはいつもsome Viewを使用します。なぜなら、各Viewのbodyは単一の具体的な型だからです。
Opaque TypeはSwiftの根本的な問題を解決します: 関連型を持つプロトコル(PAT)は型として直接使用できません。関数は単にCollectionを戻すことができません — コンパイラはElementを指定するよう求めます。some Collectionは関連型を隠すことでこれを解決します。
Opaque typeなしでは、Collectionを戻すには具体的な型(Array
func makeReversedCollection<T>(
of array: [T]
) -> some Collection {
return array.reversed()
}
let result = makeReversedCollection(of: [1, 2, 3])
// result — ReversedCollection>, hidden from caller
for item in result {
print(item)
}
resultは反復可能ですが、ReversedCollectionのプロパティに直接アクセスすることはできません。これはエンキャプセレーションを保護します: 後でreversed()を異なる実装の別のメソッドに置き換えても、コール側のコードは壊れません。Opaque typeはAPIを変えずに実装を変更する自由を与えます。
some Viewはopaque typeの最も有名な使用例です。SwiftUIの各Viewはbodyをsome Viewとして宣言します。これは、bodyがいくつかの具体的なViewの型を戻すことを意味しますが、開発者は実際にどの型かを考える必要がありません — TupleView、Group、ModifiedContent、またはフレームワークの他の型です。
Opaque typeなしでは、bodyは具体的な型を戻す必要があります。例えばModifiedContent<Button<Text>, Padding>などで、現実的ではありません。some Viewはこの複雑さを隠します。コンパイラはコンパイル時にbodyの正確な型を自動的に推論します。
struct ContentView: View {
var body: some View {
VStack {
Text("こんにちは")
.font(.title)
Button("タップしてください") {
print("タップされました")
}
}
.padding()
}
}
コンパイラはbodyをModifiedContent<VStack<TupleView<(Text, Button<Text>)>>, Padding>として推論します。開発者はsome Viewを見ます。レイアウトをVStackからHStackに変更すると、コンパイラは自動的に型を再推論します — 手動での編集は不要です。これがopaque typeの魔法です: 開発者はインタフェースのロジックに集中し、構成タイプには集中しません。
よくある質問
Opaque Typeはsomeキーワードで宣言される型で、コール側から具体的な実装を隠します。コンパイラは正確な型を知っていますが、関数を使用する開発者はプロトコルしか見えません。
someは静的なアイデンティティを持つopaque typeです: コンパイラは具体的な型を知っています。anyは動的ディスパッチを持つ存在的型です: 型のアイデンティティは消されます。Someは効率的で、anyは柔軟です。
some Viewは、コンパイラが自動的に推論するbodyの複雑な具体的な型を隠します。これにより開発者は、ジェネリックなラッパー(VStack、Group、ModifiedContent)からなる正確な型を書く必要がありません。
はい、Swift 5.7から可能です。パラメータでのsomeはジェネリックパラメータの構文糖です。関数宣言を簡素化し、特に各someパラメータが独立した
コンパイラはエラーを出します: opaque typeはすべての戻り値のブランチが同じ具体的な型を戻すことを必要とします。これは型のアイデンティティを保持するための意図的な設計です。異なる型を戻す必要があれば、anyを使用してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。