Opaque Type(不透明型)は、Swift 5.1で導入された概念で、Opaque Return Type(不透明返却型)とも呼ばれます。 プロトコルやジェネリクス、特にAssociated Typeを持つプロトコルの使用を簡素化します。この記事では、Opaque Typeとは何か、そしてどのように使うのかを解説します。
プロトコルとジェネリクスの課題
Associated Typeを持つプロトコルを関数の戻り値型として使用する場合、具体的なデータ型を指定することが難しい場合があります。 例えば:
protocol P {
associatedtype Value
var value: Value { get }
init(value: Value)
}
struct S1 : P {
var value: Int
}
struct S2: P {
var value: String
}
// エラー: 戻り値の型を推測できません
// func foo() -> P { return S1() }
ジェネリクスを使用する場合も、関数の引数や戻り値の型を明示的に指定する必要があるため、制限があります。
func foo<T: P>(value: T.Value) -> T { return T(value: value) }
let s1: S1 = foo(value: 10)
let s2: S2 = foo(value: "ahihi")
Opaque Typeによる解決策
some
キーワードで宣言されるOpaque Typeを使用すると、関数はプロトコルに準拠する「隠された」データ型を返すことができます。具体的な型を指定する必要はありません。
func foo() -> some P { return S1(value: 11) }
上記の例では、some P
はプロトコルP
を満たす具体的なデータ型を表し、コンパイラが自動的に推論します。
ただし、Opaque Typeは同じ関数内で1つのデータ型のみを返すことができます。
// エラー: 複数の異なる型を返すことはできません
// func bar(_ x: Int) -> some P {
// if x > 10 { return S1(value: 12) }
// else { return S2(value: "ahihi") }
// }
Opaque TypeとPATs (Protocols with Associated Types)
Opaque Typeは、PATsを扱う際に特に役立ちます。 具体的なデータ型を知らなくても、PATsを戻り値型として使用できるため、コードの柔軟性が高まり、ライブラリのバージョンに縛られることが少なくなります。
func giveMeACollection() -> some Collection { return [1, 2, 3] }
let collection = giveMeACollection()
print(collection.count) // 3
Opaque Typeと具体的な型
Opaque Typeを持つ関数が具体的な型を返す場合、コンパイラは、その関数への呼び出しが常に同じ型を返すことを保証します。
func fooo() -> some Equatable { return 5 }
let f1 = fooo()
let f2 = fooo()
print(f1 == f2) // true
ただし、これはPATsには当てはまりません。 Opaque TypeとPATsを持つ各関数は、たとえ同じプロトコルに準拠していても、別々のデータ型を返します。
Opaque Typeとジェネリックプレースホルダー
Opaque Typeとジェネリックプレースホルダーを組み合わせることで、複数のデータ型に対応できる汎用的な関数を、具体的な型を返さずに作成できます。
なぜOpaque Typeを使うべきなのか?
Opaque Typeは、プロトコル、特にPATsを扱う際に柔軟性をもたらします。 具体的なデータ型を指定する必要がなくなり、ライブラリのバージョンへの依存を減らし、コードの再利用性を高めます。
SwiftUIにおけるOpaque Type
Opaque TypeはSwiftUIで広く使用されています。 View
プロトコルのbody
プロパティはsome View
という型を持ち、View
プロトコルに準拠する任意のデータ型を返すことができます。 これにより、ユーザーインターフェースの構築が簡素化されます。 some View
を使用することで、View
が複数階層にネストされている場合に、複雑なデータ型を避けることができます。