[Swift] 「publicなプロトコルへの準拠」だけを外部に公開する
以前書いた記事でジェネリック型の型パラメータの隠蔽を行おうとして、結果ジェネリック型ごと隠蔽し、内部実装を過剰に隠蔽してしまいました。
今回の方法はその改良版です。
ヒントにしたのはSwiftUIです。SwiftUIのView
というのは以下のようなプロトコルです。
protocol View {
associatedtype Body: View
var body: Body { get }
}
Body
を知らなかった人も多いかもしれません。実は以下のようにbody
を宣言するとき、Body
はsome View
として自動で推論されるのです。
var body: some View {
/* ... */
}
このため、外部にはMyView.Body
のような型はsome View
として公開されます。
このことを利用すると、型をOpaqueにできます。今回は簡単な例で、以下の状況を考えましょう。
public protocol MyPublicUsable {
static func test()
}
private struct MyPrivateStruct: MyPublicUsable {
static func test() {}
}
上と同じ方法を使ってMyPrivateStruct
をMyPublicUsable
に準拠したPublicStruct
として公開します。
// 型をOpaqueにするためのprotocol(上記で言うViewに対応)
public protocol OpaqueTypeBuilderProtocol {
associatedtype PublicStruct: MyPublicUsable
func publicStruct() -> PublicStruct
}
// 型をOpaqueにするための具体的な型(上記で言うMyViewなどに対応)
public enum _OpaqueTypeBuilder: OpaqueTypeBuilderProtocol {
public func publicStruct() -> some MyPublicUsable {
return MyPrivateStruct()
}
}
// associatedtypeを取り出して公開する
public typealias PublicStruct = _OpaqueTypeBuilder.PublicStruct
これで、実際のところはMyPrivateStruct
である型をPublicStruct
として公開することができました。この型がMyPublicUsable
であることは分かっているため、MyPublicUsable
に準拠した「何らかの型」として扱うことは可能です。しかしMyPublicUsable
に関係のないプロパティやメソッドは引き続き公開されずに済みます。
このため、もちろんジェネリック型の型パラメータとしても使えます。
public struct MyWrapper<Value: MyPublicUsable> {}
// 別のモジュールで以下のように使える。
let value: MyWrapper<PublicStruct> = .init()
なお、_OpaqueTypeBuilder
にpublicStruct
という関数を作りましたが、この関数を実際に呼ぶことはありません。そのため、パフォーマンス上のデメリットもありません。
複数の型をOpaqueにしたい場合は次のようにOpaqueTypeBuilderProtocol
の制約を増やしていくことができます。
public protocol OpaqueTypeBuilderProtocol {
associatedtype PublicStruct1: MyPublicUsable
func publicStruct1() -> PublicStruct1
associatedtype PublicStruct2: MyPublicUsable
func publicStruct2() -> PublicStruct2
associatedtype PublicStruct3: MyPublicUsable
func publicStruct3() -> PublicStruct3
}
public enum _OpaqueTypeBuilder: OpaqueTypeBuilderProtocol {
public func publicStruct1() -> some MyPublicUsable {
return MyPrivateStruct1()
}
public func publicStruct2() -> some MyPublicUsable {
return MyPrivateStruct2()
}
public func publicStruct3() -> some MyPublicUsable {
return MyPrivateStruct3()
}
}
public typealias PublicStruct1 = _OpaqueTypeBuilder.PublicStruct1
public typealias PublicStruct2 = _OpaqueTypeBuilder.PublicStruct2
public typealias PublicStruct3 = _OpaqueTypeBuilder.PublicStruct3
こうすることで、任意の型の「プロトコルへの準拠」という側面だけを外部に公開することができるようになりました。実装を過剰に公開することも過剰に隠蔽することもないので、なかなか良い方法だと思います。
Discussion